@ -46,6 +46,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly Version _minFFmpegImplictHwaccel = new Version ( 6 , 0 ) ;
private readonly Version _minFFmpegHwaUnsafeOutput = new Version ( 6 , 0 ) ;
private readonly Version _minFFmpegOclCuTonemapMode = new Version ( 5 , 1 , 3 ) ;
private readonly Version _minFFmpegSvtAv1Params = new Version ( 5 , 1 ) ;
private static readonly string [ ] _videoProfilesH264 = new [ ]
{
@ -65,6 +66,13 @@ namespace MediaBrowser.Controller.MediaEncoding
"Main10"
} ;
private static readonly string [ ] _videoProfilesAv1 = new [ ]
{
"Main" ,
"High" ,
"Professional" ,
} ;
private static readonly HashSet < string > _mp4ContainerNames = new ( StringComparer . OrdinalIgnoreCase )
{
"mp4" ,
@ -113,12 +121,15 @@ namespace MediaBrowser.Controller.MediaEncoding
}
public string GetH264Encoder ( EncodingJobInfo state , EncodingOptions encodingOptions )
= > GetH26 4OrH265 Encoder( "libx264" , "h264" , state , encodingOptions ) ;
= > GetH26 xOrAv1 Encoder( "libx264" , "h264" , state , encodingOptions ) ;
public string GetH265Encoder ( EncodingJobInfo state , EncodingOptions encodingOptions )
= > GetH264OrH265Encoder ( "libx265" , "hevc" , state , encodingOptions ) ;
= > GetH26xOrAv1Encoder ( "libx265" , "hevc" , state , encodingOptions ) ;
public string GetAv1Encoder ( EncodingJobInfo state , EncodingOptions encodingOptions )
= > GetH26xOrAv1Encoder ( "libsvtav1" , "av1" , state , encodingOptions ) ;
private string GetH264OrH265Encoder ( string defaultEncoder , string hwEncoder , EncodingJobInfo state , EncodingOptions encodingOptions )
private string GetH26 xOrAv1 Encoder( string defaultEncoder , string hwEncoder , EncodingJobInfo state , EncodingOptions encodingOptions )
{
// Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
@ -266,6 +277,11 @@ namespace MediaBrowser.Controller.MediaEncoding
if ( ! string . IsNullOrEmpty ( codec ) )
{
if ( string . Equals ( codec , "av1" , StringComparison . OrdinalIgnoreCase ) )
{
return GetAv1Encoder ( state , encodingOptions ) ;
}
if ( string . Equals ( codec , "h265" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( codec , "hevc" , StringComparison . OrdinalIgnoreCase ) )
{
@ -565,6 +581,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return Array . FindIndex ( _videoProfilesH265 , x = > string . Equals ( x , profile , StringComparison . OrdinalIgnoreCase ) ) ;
}
if ( string . Equals ( "av1" , videoCodec , StringComparison . OrdinalIgnoreCase ) )
{
return Array . FindIndex ( _videoProfilesAv1 , x = > string . Equals ( x , profile , StringComparison . OrdinalIgnoreCase ) ) ;
}
return - 1 ;
}
@ -1204,6 +1225,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return FormattableString . Invariant ( $" -b:v {bitrate}" ) ;
}
if ( string . Equals ( videoCodec , "libsvtav1" , StringComparison . OrdinalIgnoreCase ) )
{
return FormattableString . Invariant ( $" -b:v {bitrate} -bufsize {bufsize}" ) ;
}
if ( string . Equals ( videoCodec , "libx264" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoCodec , "libx265" , StringComparison . OrdinalIgnoreCase ) )
{
@ -1211,14 +1237,16 @@ namespace MediaBrowser.Controller.MediaEncoding
}
if ( string . Equals ( videoCodec , "h264_amf" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoCodec , "hevc_amf" , StringComparison . OrdinalIgnoreCase ) )
| | string . Equals ( videoCodec , "hevc_amf" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoCodec , "av1_amf" , StringComparison . OrdinalIgnoreCase ) )
{
// Override the too high default qmin 18 in transcoding preset
return FormattableString . Invariant ( $" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}" ) ;
}
if ( string . Equals ( videoCodec , "h264_vaapi" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoCodec , "hevc_vaapi" , StringComparison . OrdinalIgnoreCase ) )
| | string . Equals ( videoCodec , "hevc_vaapi" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoCodec , "av1_vaapi" , StringComparison . OrdinalIgnoreCase ) )
{
// VBR in i965 driver may result in pixelated output.
if ( _mediaEncoder . IsVaapiDeviceInteli965 )
@ -1236,14 +1264,23 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if ( double . TryParse ( level , CultureInfo . InvariantCulture , out double requestLevel ) )
{
if ( string . Equals ( state . ActualOutputVideoCodec , "hevc" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( state . ActualOutputVideoCodec , "h265" , StringComparison . OrdinalIgnoreCase ) )
if ( string . Equals ( state . ActualOutputVideoCodec , "av1" , StringComparison . OrdinalIgnoreCase ) )
{
// Transcode to level 5.3 (15) and lower for maximum compatibility.
// https://en.wikipedia.org/wiki/AV1#Levels
if ( requestLevel < 0 | | requestLevel > = 15 )
{
return "15" ;
}
}
else if ( string . Equals ( state . ActualOutputVideoCodec , "hevc" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( state . ActualOutputVideoCodec , "h265" , StringComparison . OrdinalIgnoreCase ) )
{
// Transcode to level 5.0 and lower for maximum compatibility.
// Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
// https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
// MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
if ( requestLevel > = 150 )
if ( requestLevel < 0 | | requestLevel >= 150 )
{
return "150" ;
}
@ -1253,7 +1290,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Transcode to level 5.1 and lower for maximum compatibility.
// h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
// https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
if ( requestLevel >= 51 )
if ( requestLevel < 0 | | requestLevel >= 51 )
{
return "51" ;
}
@ -1391,14 +1428,18 @@ namespace MediaBrowser.Controller.MediaEncoding
| | 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 ) )
| | string . Equals ( codec , "av1_qsv" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( codec , "av1_nvenc" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( codec , "av1_amf" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( codec , "libsvtav1" , StringComparison . OrdinalIgnoreCase ) )
{
args + = gopArg ;
}
else if ( string . Equals ( codec , "libx264" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( codec , "libx265" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( codec , "h264_vaapi" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( codec , "hevc_vaapi" , StringComparison . OrdinalIgnoreCase ) )
| | string . Equals ( codec , "hevc_vaapi" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( codec , "av1_vaapi" , StringComparison . OrdinalIgnoreCase ) )
{
args + = keyFrameArg ;
@ -1534,18 +1575,60 @@ namespace MediaBrowser.Controller.MediaEncoding
param + = " -crf " + defaultCrf ;
}
}
else if ( string . Equals ( videoEncoder , "libsvtav1" , StringComparison . OrdinalIgnoreCase ) )
{
// Default to use the recommended preset 10.
// Omit presets < 5, which are too slow for on the fly encoding.
// https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
param + = encodingOptions . EncoderPreset switch
{
"veryslow" = > " -preset 5" ,
"slower" = > " -preset 6" ,
"slow" = > " -preset 7" ,
"medium" = > " -preset 8" ,
"fast" = > " -preset 9" ,
"faster" = > " -preset 10" ,
"veryfast" = > " -preset 11" ,
"superfast" = > " -preset 12" ,
"ultrafast" = > " -preset 13" ,
_ = > " -preset 10"
} ;
}
else if ( string . Equals ( videoEncoder , "h264_vaapi" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoEncoder , "hevc_vaapi" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoEncoder , "av1_vaapi" , StringComparison . OrdinalIgnoreCase ) )
{
// -compression_level is not reliable on AMD.
if ( _mediaEncoder . IsVaapiDeviceInteliHD )
{
param + = encodingOptions . EncoderPreset switch
{
"veryslow" = > " -compression_level 1" ,
"slower" = > " -compression_level 2" ,
"slow" = > " -compression_level 3" ,
"medium" = > " -compression_level 4" ,
"fast" = > " -compression_level 5" ,
"faster" = > " -compression_level 6" ,
"veryfast" = > " -compression_level 7" ,
"superfast" = > " -compression_level 7" ,
"ultrafast" = > " -compression_level 7" ,
_ = > string . Empty
} ;
}
}
else if ( string . Equals ( videoEncoder , "h264_qsv" , StringComparison . OrdinalIgnoreCase ) // h264 (h264_qsv)
| | string . Equals ( videoEncoder , "hevc_qsv" , StringComparison . OrdinalIgnoreCase ) ) // hevc (hevc_qsv)
| | string . Equals ( videoEncoder , "hevc_qsv" , StringComparison . OrdinalIgnoreCase ) // hevc (hevc_qsv)
| | string . Equals ( videoEncoder , "av1_qsv" , StringComparison . OrdinalIgnoreCase ) ) // av1 (av1_qsv)
{
string [ ] valid_h264_qsv = { "veryslow" , "slower" , "slow" , "medium" , "fast" , "faster" , "veryfast" } ;
string [ ] valid_ presets = { "veryslow" , "slower" , "slow" , "medium" , "fast" , "faster" , "veryfast" } ;
if ( valid_h264_qsv . Contains ( encodingOptions . EncoderPreset , StringComparison . OrdinalIgnoreCase ) )
if ( valid_ presets . Contains ( encodingOptions . EncoderPreset , StringComparison . OrdinalIgnoreCase ) )
{
param + = " -preset " + encodingOptions . EncoderPreset ;
}
else
{
param + = " -preset 7" ;
param + = " -preset veryfast ";
}
// Only h264_qsv has look_ahead option
@ -1555,7 +1638,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
else if ( string . Equals ( videoEncoder , "h264_nvenc" , StringComparison . OrdinalIgnoreCase ) // h264 (h264_nvenc)
| | string . Equals ( videoEncoder , "hevc_nvenc" , StringComparison . OrdinalIgnoreCase ) ) // hevc (hevc_nvenc)
| | string . Equals ( videoEncoder , "hevc_nvenc" , StringComparison . OrdinalIgnoreCase ) // hevc (hevc_nvenc)
| | string . Equals ( videoEncoder , "av1_nvenc" , StringComparison . OrdinalIgnoreCase ) ) // av1 (av1_nvenc)
{
switch ( encodingOptions . EncoderPreset )
{
@ -1595,7 +1679,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
else if ( string . Equals ( videoEncoder , "h264_amf" , StringComparison . OrdinalIgnoreCase ) // h264 (h264_amf)
| | string . Equals ( videoEncoder , "hevc_amf" , StringComparison . OrdinalIgnoreCase ) ) // hevc (hevc_amf)
| | string . Equals ( videoEncoder , "hevc_amf" , StringComparison . OrdinalIgnoreCase ) // hevc (hevc_amf)
| | string . Equals ( videoEncoder , "av1_amf" , StringComparison . OrdinalIgnoreCase ) ) // av1 (av1_amf)
{
switch ( encodingOptions . EncoderPreset )
{
@ -1622,9 +1707,15 @@ namespace MediaBrowser.Controller.MediaEncoding
break ;
}
if ( string . Equals ( videoEncoder , "hevc_amf" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoEncoder , "av1_amf" , StringComparison . OrdinalIgnoreCase ) )
{
param + = " -header_insertion_mode gop" ;
}
if ( string . Equals ( videoEncoder , "hevc_amf" , StringComparison . OrdinalIgnoreCase ) )
{
param + = " -header_insertion_mode gop -gops_per_idr 1" ;
param + = " - gops_per_idr 1";
}
}
else if ( string . Equals ( videoEncoder , "libvpx" , StringComparison . OrdinalIgnoreCase ) ) // vp8
@ -1755,6 +1846,14 @@ namespace MediaBrowser.Controller.MediaEncoding
profile = "high" ;
}
// We only need Main profile of AV1 encoders.
if ( videoEncoder . Contains ( "av1" , StringComparison . OrdinalIgnoreCase )
& & ( profile . Contains ( "high" , StringComparison . OrdinalIgnoreCase )
| | profile . Contains ( "professional" , StringComparison . OrdinalIgnoreCase ) ) )
{
profile = "main" ;
}
// 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 )
@ -1822,19 +1921,41 @@ namespace MediaBrowser.Controller.MediaEncoding
param + = " -level " + ( hevcLevel / 3 ) ;
}
}
else if ( string . Equals ( videoEncoder , "av1_qsv" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoEncoder , "libsvtav1" , StringComparison . OrdinalIgnoreCase ) )
{
// libsvtav1 and av1_qsv use -level 60 instead of -level 16
// https://aomedia.org/av1/specification/annex-a/
if ( int . TryParse ( level , NumberStyles . Any , CultureInfo . InvariantCulture , out int av1Level ) )
{
var x = 2 + ( av1Level > > 2 ) ;
var y = av1Level & 3 ;
var res = ( x * 10 ) + y ;
param + = " -level " + res ;
}
}
else if ( string . Equals ( videoEncoder , "h264_amf" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoEncoder , "hevc_amf" , StringComparison . OrdinalIgnoreCase ) )
| | string . Equals ( videoEncoder , "hevc_amf" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoEncoder , "av1_amf" , StringComparison . OrdinalIgnoreCase ) )
{
param + = " -level " + level ;
}
else if ( string . Equals ( videoEncoder , "h264_nvenc" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoEncoder , "hevc_nvenc" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoEncoder , "h264_vaapi" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoEncoder , "hevc_vaapi" , StringComparison . OrdinalIgnoreCase ) )
| | string . Equals ( videoEncoder , "av1_nvenc" , StringComparison . OrdinalIgnoreCase ) )
{
// level option may cause NVENC to fail.
// NVENC cannot adjust the given level, just throw an error.
}
else if ( string . Equals ( videoEncoder , "h264_vaapi" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoEncoder , "hevc_vaapi" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( videoEncoder , "av1_vaapi" , StringComparison . OrdinalIgnoreCase ) )
{
// level option may cause corrupted frames on AMD VAAPI.
if ( _mediaEncoder . IsVaapiDeviceInteliHD | | _mediaEncoder . IsVaapiDeviceInteli965 )
{
param + = " -level " + level ;
}
}
else if ( ! string . Equals ( videoEncoder , "libx265" , StringComparison . OrdinalIgnoreCase ) )
{
@ -1856,6 +1977,12 @@ namespace MediaBrowser.Controller.MediaEncoding
param + = " -x265-params:0 no-info=1" ;
}
if ( string . Equals ( videoEncoder , "libsvtav1" , StringComparison . OrdinalIgnoreCase )
& & _mediaEncoder . EncoderVersion > = _minFFmpegSvtAv1Params )
{
param + = " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0" ;
}
return param ;
}
@ -3645,7 +3772,7 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters . Add ( swDeintFilter ) ;
}
var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p" ;
var outFormat = doOclTonemap ? "yuv420p10le" : ( hasGraphicalSubs ? "yuv420p" : "nv12" ) ;
var swScaleFilter = GetSwScaleFilter ( state , options , vidEncoder , inW , inH , threeDFormat , reqW , reqH , reqMaxW , reqMaxH ) ;
// sw scale
mainFilters . Add ( swScaleFilter ) ;
@ -3846,7 +3973,7 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters . Add ( swDeintFilter ) ;
}
var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p" ;
var outFormat = doOclTonemap ? "yuv420p10le" : ( hasGraphicalSubs ? "yuv420p" : "nv12" ) ;
var swScaleFilter = GetSwScaleFilter ( state , options , vidEncoder , inW , inH , threeDFormat , reqW , reqH , reqMaxW , reqMaxH ) ;
// sw scale
mainFilters . Add ( swScaleFilter ) ;
@ -5810,19 +5937,25 @@ namespace MediaBrowser.Controller.MediaEncoding
private void ShiftVideoCodecsIfNeeded ( List < string > videoCodecs , EncodingOptions encodingOptions )
{
// Shift hevc/h265 to the end of list if hevc encoding is not allowed .
if ( encodingOptions. AllowHevcEncoding )
// No need to shift if there is only one supported video codec .
if ( videoCodecs. Count < 2 )
{
return ;
}
// No need to shift if there is only one supported video codec.
if ( videoCodecs . Count < 2 )
// Shift codecs to the end of list if it's not allowed.
var shiftVideoCodecs = new List < string > ( ) ;
if ( ! encodingOptions . AllowHevcEncoding )
{
return ;
shiftVideoCodecs . Add ( "hevc" ) ;
shiftVideoCodecs . Add ( "h265" ) ;
}
if ( ! encodingOptions . AllowAv1Encoding )
{
shiftVideoCodecs . Add ( "av1" ) ;
}
var shiftVideoCodecs = new [ ] { "hevc" , "h265" } ;
if ( videoCodecs . All ( i = > shiftVideoCodecs . Contains ( i , StringComparison . OrdinalIgnoreCase ) ) )
{
return ;