@ -31,10 +31,13 @@ namespace MediaBrowser.Controller.MediaEncoding
private const string VideotoolboxAlias = "vt" ;
private const string OpenclAlias = "ocl" ;
private const string CudaAlias = "cu" ;
private const string DrmAlias = "dr" ;
private const string VulkanAlias = "vk" ;
private readonly IApplicationPaths _appPaths ;
private readonly IMediaEncoder _mediaEncoder ;
private readonly ISubtitleEncoder _subtitleEncoder ;
private readonly IConfiguration _config ;
private readonly Version _minKernelVersionAmdVkFmtModifier = new Version ( 5 , 15 ) ;
private readonly Version _minKernelVersioni915Hang = new Version ( 5 , 18 ) ;
private static readonly string [ ] _videoProfilesH264 = new [ ]
@ -149,6 +152,14 @@ namespace MediaBrowser.Controller.MediaEncoding
& & _mediaEncoder . SupportsFilter ( "hwupload_cuda" ) ;
}
private bool IsVulkanFullSupported ( )
{
return _mediaEncoder . SupportsHwaccel ( "vulkan" )
& & _mediaEncoder . SupportsFilter ( "libplacebo" )
& & _mediaEncoder . SupportsFilter ( "scale_vulkan" )
& & _mediaEncoder . SupportsFilterWithOption ( FilterOptionType . OverlayVulkanFrameSync ) ;
}
private bool IsHwTonemapAvailable ( EncodingJobInfo state , EncodingOptions options )
{
if ( state . VideoStream = = null
@ -176,6 +187,19 @@ namespace MediaBrowser.Controller.MediaEncoding
| | string . Equals ( state . VideoStream . VideoRangeType , "HLG" , StringComparison . OrdinalIgnoreCase ) ) ;
}
private bool IsVulkanHwTonemapAvailable ( EncodingJobInfo state , EncodingOptions options )
{
if ( state . VideoStream = = null )
{
return false ;
}
// libplacebo has partial Dolby Vision to SDR tonemapping support.
return options . EnableTonemapping
& & string . Equals ( state . VideoStream . VideoRange , "HDR" , StringComparison . OrdinalIgnoreCase )
& & GetVideoColorBitDepth ( state ) = = 10 ;
}
private bool IsVaapiVppTonemapAvailable ( EncodingJobInfo state , EncodingOptions options )
{
if ( state . VideoStream = = null
@ -756,8 +780,13 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if ( _mediaEncoder . IsVaapiDeviceAmd )
{
args . Append ( GetOpenclDeviceArgs ( 0 , "Advanced Micro Devices" , null , OpenclAlias ) ) ;
filterDevArgs = GetFilterHwDeviceArgs ( OpenclAlias ) ;
if ( ! IsVulkanFullSupported ( )
| | ! _mediaEncoder . IsVaapiDeviceSupportVulkanFmtModifier
| | Environment . OSVersion . Version < _minKernelVersionAmdVkFmtModifier )
{
args . Append ( GetOpenclDeviceArgs ( 0 , "Advanced Micro Devices" , null , OpenclAlias ) ) ;
filterDevArgs = GetFilterHwDeviceArgs ( OpenclAlias ) ;
}
}
else
{
@ -2774,22 +2803,41 @@ namespace MediaBrowser.Controller.MediaEncoding
return string . Empty ;
}
var args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709" ;
var args = string . Empty ;
var algorithm = options . TonemappingAlgorithm ;
if ( hwTonemapSuffix . Contains ( "vaapi" , StringComparison . OrdinalIgnoreCase ) )
if ( string . Equals ( hwTonemapSuffix , "vaapi" , StringComparison . OrdinalIgnoreCase ) )
{
args += ",procamp_vaapi=b={2}:c={3 }:extra_hw_frames=16";
args = "tonemap_vaapi=format={0}:p=bt709:t=bt709:m=bt709,procamp_vaapi=b={1}:c={2 }:extra_hw_frames=16";
return string . Format (
CultureInfo . InvariantCulture ,
args ,
hwTonemapSuffix ,
videoFormat ? ? "nv12" ,
options . VppTonemappingBrightness ,
options . VppTonemappingContrast ) ;
}
else if ( string . Equals ( hwTonemapSuffix , "vulkan" , StringComparison . OrdinalIgnoreCase ) )
{
args = "libplacebo=format={1}:tonemapping={2}:color_primaries=bt709:color_trc=bt709:colorspace=bt709:peak_detect=0:upscaler=none:downscaler=none" ;
if ( ! string . Equals ( options . TonemappingRange , "auto" , StringComparison . OrdinalIgnoreCase ) )
{
args + = ":range={6}" ;
}
if ( string . Equals ( options . TonemappingAlgorithm , "bt2390" , StringComparison . OrdinalIgnoreCase ) )
{
algorithm = "bt.2390" ;
}
else if ( string . Equals ( options . TonemappingAlgorithm , "none" , StringComparison . OrdinalIgnoreCase ) )
{
algorithm = "clip" ;
}
}
else
{
args + = ":tonemap={2}:peak={3}:desat={4}" ;
args = " tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709 :tonemap={2}:peak={3}:desat={4}";
if ( options . TonemappingParam ! = 0 )
{
@ -2807,7 +2855,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args ,
hwTonemapSuffix ,
videoFormat ? ? "nv12" ,
options. Tonem appingA lgorithm,
algorithm,
options . TonemappingPeak ,
options . TonemappingDesat ,
options . TonemappingParam ,
@ -3770,7 +3818,9 @@ namespace MediaBrowser.Controller.MediaEncoding
var vidDecoder = GetHardwareVideoDecoder ( state , options ) ? ? string . Empty ;
var isSwDecoder = string . IsNullOrEmpty ( vidDecoder ) ;
var isSwEncoder = ! vidEncoder . Contains ( "vaapi" , StringComparison . OrdinalIgnoreCase ) ;
var isVaapiOclSupported = isLinux & & IsVaapiSupported ( state ) & & IsVaapiFullSupported ( ) & & IsOpenclFullSupported ( ) ;
var isVaapiFullSupported = isLinux & & IsVaapiSupported ( state ) & & IsVaapiFullSupported ( ) ;
var isVaapiOclSupported = isVaapiFullSupported & & IsOpenclFullSupported ( ) ;
var isVaapiVkSupported = isVaapiFullSupported & & IsVulkanFullSupported ( ) ;
// legacy vaapi pipeline(copy-back)
if ( ( isSwDecoder & & isSwEncoder )
@ -3798,14 +3848,24 @@ namespace MediaBrowser.Controller.MediaEncoding
if ( _mediaEncoder . IsVaapiDeviceInteliHD )
{
// Intel iHD path, with extra vpp tonemap and overlay support.
return GetVaapiFullVidFiltersPrefered ( state , options , vidDecoder , vidEncoder ) ;
return GetIntelVaapiFullVidFiltersPrefered ( state , options , vidDecoder , vidEncoder ) ;
}
// prefered vaapi + vulkan filters pipeline
if ( _mediaEncoder . IsVaapiDeviceAmd
& & isVaapiVkSupported
& & _mediaEncoder . IsVaapiDeviceSupportVulkanFmtModifier
& & Environment . OSVersion . Version > = _minKernelVersionAmdVkFmtModifier )
{
// AMD radeonsi path(Vega/gfx9+, kernel>=5.15), with extra vulkan tonemap and overlay support.
return GetAmdVaapiFullVidFiltersPrefered ( state , options , vidDecoder , vidEncoder ) ;
}
// Intel i965 and Amd radeonsi/r600 path, only featuring scale and deinterlace support.
// Intel i965 and Amd radeonsi/r600 path (Polaris/gfx8-) , only featuring scale and deinterlace support.
return GetVaapiLimitedVidFiltersPrefered ( state , options , vidDecoder , vidEncoder ) ;
}
public ( List < string > MainFilters , List < string > SubFilters , List < string > OverlayFilters ) GetVaapiFullVidFiltersPrefered (
public ( List < string > MainFilters , List < string > SubFilters , List < string > OverlayFilters ) Get Intel VaapiFullVidFiltersPrefered(
EncodingJobInfo state ,
EncodingOptions options ,
string vidDecoder ,
@ -4003,6 +4063,203 @@ namespace MediaBrowser.Controller.MediaEncoding
return ( mainFilters , subFilters , overlayFilters ) ;
}
public ( List < string > MainFilters , List < string > SubFilters , List < string > OverlayFilters ) GetAmdVaapiFullVidFiltersPrefered (
EncodingJobInfo state ,
EncodingOptions options ,
string vidDecoder ,
string vidEncoder )
{
var inW = state . VideoStream ? . Width ;
var inH = state . VideoStream ? . Height ;
var reqW = state . BaseRequest . Width ;
var reqH = state . BaseRequest . Height ;
var reqMaxW = state . BaseRequest . MaxWidth ;
var reqMaxH = state . BaseRequest . MaxHeight ;
var threeDFormat = state . MediaSource . Video3DFormat ;
var isVaapiDecoder = vidDecoder . Contains ( "vaapi" , StringComparison . OrdinalIgnoreCase ) ;
var isVaapiEncoder = vidEncoder . Contains ( "vaapi" , StringComparison . OrdinalIgnoreCase ) ;
var isSwDecoder = string . IsNullOrEmpty ( vidDecoder ) ;
var isSwEncoder = ! isVaapiEncoder ;
var isVaInVaOut = isVaapiDecoder & & isVaapiEncoder ;
var doDeintH264 = state . DeInterlace ( "h264" , true ) | | state . DeInterlace ( "avc" , true ) ;
var doDeintHevc = state . DeInterlace ( "h265" , true ) | | state . DeInterlace ( "hevc" , true ) ;
var doVkTonemap = IsVulkanHwTonemapAvailable ( state , options ) ;
var doDeintH2645 = doDeintH264 | | doDeintHevc ;
var hasSubs = state . SubtitleStream ! = null & & state . SubtitleDeliveryMethod = = SubtitleDeliveryMethod . Encode ;
var hasTextSubs = hasSubs & & state . SubtitleStream . IsTextSubtitleStream ;
var hasGraphicalSubs = hasSubs & & ! state . SubtitleStream . IsTextSubtitleStream ;
var hasAssSubs = hasSubs
& & ( string . Equals ( state . SubtitleStream . Codec , "ass" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( state . SubtitleStream . Codec , "ssa" , StringComparison . OrdinalIgnoreCase ) ) ;
/* Make main filters for video stream */
var mainFilters = new List < string > ( ) ;
mainFilters . Add ( GetOverwriteColorPropertiesParam ( state , doVkTonemap ) ) ;
if ( isSwDecoder )
{
// INPUT sw surface(memory)
// sw deint
if ( doDeintH2645 )
{
var swDeintFilter = GetSwDeinterlaceFilter ( state , options ) ;
mainFilters . Add ( swDeintFilter ) ;
}
var outFormat = doVkTonemap ? "yuv420p10le" : "nv12" ;
var swScaleFilter = GetSwScaleFilter ( state , options , vidEncoder , inW , inH , threeDFormat , reqW , reqH , reqMaxW , reqMaxH ) ;
// sw scale
mainFilters . Add ( swScaleFilter ) ;
mainFilters . Add ( "format=" + outFormat ) ;
// keep video at memory except vk tonemap,
// since the overhead caused by hwupload >>> using sw filter.
// sw => hw
if ( doVkTonemap )
{
mainFilters . Add ( "hwupload=derive_device=vulkan:extra_hw_frames=16" ) ;
}
}
else if ( isVaapiDecoder )
{
// INPUT vaapi surface(vram)
// hw deint
if ( doDeintH2645 )
{
var deintFilter = GetHwDeinterlaceFilter ( state , options , "vaapi" ) ;
mainFilters . Add ( deintFilter ) ;
}
var outFormat = doVkTonemap ? string . Empty : ( hasSubs & & isVaInVaOut ? "bgra" : "nv12" ) ;
var hwScaleFilter = GetHwScaleFilter ( "vaapi" , outFormat , inW , inH , reqW , reqH , reqMaxW , reqMaxH ) ;
// allocate extra pool sizes for overlay_vulkan
if ( ! string . IsNullOrEmpty ( hwScaleFilter ) & & isVaInVaOut & & hasSubs )
{
hwScaleFilter + = ":extra_hw_frames=32" ;
}
// hw scale
mainFilters . Add ( hwScaleFilter ) ;
}
if ( ( isVaapiDecoder & & doVkTonemap ) | | ( isVaInVaOut & & ( doVkTonemap | | hasSubs ) ) )
{
// map from vaapi to vulkan via vaapi-vulkan interop (Vega/gfx9+).
mainFilters . Add ( "hwmap=derive_device=vulkan" ) ;
}
// vk tonemap
if ( doVkTonemap )
{
var outFormat = isVaInVaOut & & hasSubs ? "bgra" : "nv12" ;
var tonemapFilter = GetHwTonemapFilter ( options , "vulkan" , outFormat ) ;
mainFilters . Add ( tonemapFilter ) ;
}
if ( doVkTonemap & & isVaInVaOut & & ! hasSubs )
{
// OUTPUT vaapi(nv12/bgra) surface(vram)
// reverse-mapping via vaapi-vulkan interop.
mainFilters . Add ( "hwmap=derive_device=vaapi:reverse=1" ) ;
mainFilters . Add ( "format=vaapi" ) ;
}
var memoryOutput = false ;
var isUploadForVkTonemap = isSwDecoder & & doVkTonemap ;
if ( ( isVaapiDecoder & & isSwEncoder ) | | isUploadForVkTonemap )
{
memoryOutput = true ;
// OUTPUT nv12 surface(memory)
mainFilters . Add ( "hwdownload" ) ;
mainFilters . Add ( "format=nv12" ) ;
}
// OUTPUT nv12 surface(memory)
if ( isSwDecoder & & isVaapiEncoder )
{
memoryOutput = true ;
}
if ( memoryOutput )
{
// text subtitles
if ( hasTextSubs )
{
var textSubtitlesFilter = GetTextSubtitlesFilter ( state , false , false ) ;
mainFilters . Add ( textSubtitlesFilter ) ;
}
}
if ( memoryOutput & & isVaapiEncoder )
{
if ( ! hasGraphicalSubs )
{
mainFilters . Add ( "hwupload_vaapi" ) ;
}
}
/* Make sub and overlay filters for subtitle stream */
var subFilters = new List < string > ( ) ;
var overlayFilters = new List < string > ( ) ;
if ( isVaInVaOut )
{
if ( hasSubs )
{
if ( hasGraphicalSubs )
{
// scale=s=1280x720,format=bgra,hwupload
var subSwScaleFilter = GetCustomSwScaleFilter ( inW , inH , reqW , reqH , reqMaxW , reqMaxH ) ;
subFilters . Add ( subSwScaleFilter ) ;
subFilters . Add ( "format=bgra" ) ;
}
else if ( hasTextSubs )
{
var alphaSrcFilter = GetAlphaSrcFilter ( state , inW , inH , reqW , reqH , reqMaxW , reqMaxH , hasAssSubs ? 10 : 5 ) ;
var subTextSubtitlesFilter = GetTextSubtitlesFilter ( state , true , true ) ;
subFilters . Add ( alphaSrcFilter ) ;
subFilters . Add ( "format=bgra" ) ;
subFilters . Add ( subTextSubtitlesFilter ) ;
}
subFilters . Add ( "hwupload=derive_device=vulkan:extra_hw_frames=16" ) ;
overlayFilters . Add ( "overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0" ) ;
// explicitly sync using libplacebo.
overlayFilters . Add ( "libplacebo=format=nv12:upscaler=none:downscaler=none" ) ;
// OUTPUT vaapi(nv12/bgra) surface(vram)
// reverse-mapping via vaapi-vulkan interop.
overlayFilters . Add ( "hwmap=derive_device=vaapi:reverse=1" ) ;
overlayFilters . Add ( "format=vaapi" ) ;
}
}
else if ( memoryOutput )
{
if ( hasGraphicalSubs )
{
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter ( state , options , vidEncoder , inW , inH , threeDFormat , reqW , reqH , reqMaxW , reqMaxH )
: GetCustomSwScaleFilter ( inW , inH , reqW , reqH , reqMaxW , reqMaxH ) ;
subFilters . Add ( subSwScaleFilter ) ;
overlayFilters . Add ( "overlay=eof_action=pass:shortest=1:repeatlast=0" ) ;
if ( isVaapiEncoder )
{
overlayFilters . Add ( "hwupload_vaapi" ) ;
}
}
}
return ( mainFilters , subFilters , overlayFilters ) ;
}
public ( List < string > MainFilters , List < string > SubFilters , List < string > OverlayFilters ) GetVaapiLimitedVidFiltersPrefered (
EncodingJobInfo state ,
EncodingOptions options ,