@ -253,6 +253,14 @@ namespace MediaBrowser.Controller.MediaEncoding
& & _mediaEncoder . SupportsFilterWithOption ( FilterOptionType . OverlayVulkanFrameSync ) ;
& & _mediaEncoder . SupportsFilterWithOption ( FilterOptionType . OverlayVulkanFrameSync ) ;
}
}
private bool IsVideoToolBoxFullSupported ( )
{
return _mediaEncoder . SupportsHwaccel ( "videotoolbox" )
& & _mediaEncoder . SupportsFilter ( "yadif_videotoolbox" )
& & _mediaEncoder . SupportsFilter ( "overlay_videotoolbox" )
& & _mediaEncoder . SupportsFilter ( "scale_vt" ) ;
}
private bool IsHwTonemapAvailable ( EncodingJobInfo state , EncodingOptions options )
private bool IsHwTonemapAvailable ( EncodingJobInfo state , EncodingOptions options )
{
{
if ( state . VideoStream is null
if ( state . VideoStream is null
@ -272,7 +280,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var isNvdecDecoder = vidDecoder . Contains ( "cuda" , StringComparison . OrdinalIgnoreCase ) ;
var isNvdecDecoder = vidDecoder . Contains ( "cuda" , StringComparison . OrdinalIgnoreCase ) ;
var isVaapiDecoder = vidDecoder . Contains ( "vaapi" , StringComparison . OrdinalIgnoreCase ) ;
var isVaapiDecoder = vidDecoder . Contains ( "vaapi" , StringComparison . OrdinalIgnoreCase ) ;
var isD3d11vaDecoder = vidDecoder . Contains ( "d3d11va" , StringComparison . OrdinalIgnoreCase ) ;
var isD3d11vaDecoder = vidDecoder . Contains ( "d3d11va" , StringComparison . OrdinalIgnoreCase ) ;
return isSwDecoder | | isNvdecDecoder | | isVaapiDecoder | | isD3d11vaDecoder ;
var isVideoToolBoxDecoder = vidDecoder . Contains ( "videotoolbox" , StringComparison . OrdinalIgnoreCase ) ;
return isSwDecoder | | isNvdecDecoder | | isVaapiDecoder | | isD3d11vaDecoder | | isVideoToolBoxDecoder ;
}
}
return state . VideoStream . VideoRange = = VideoRange . HDR
return state . VideoStream . VideoRange = = VideoRange . HDR
@ -317,7 +326,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return false ;
return false ;
}
}
// Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce correct mapping results with tra scoding.
// Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce correct mapping results with tra n scoding.
// All other HDR formats working.
// All other HDR formats working.
return state . VideoStream . VideoRange = = VideoRange . HDR
return state . VideoStream . VideoRange = = VideoRange . HDR
& & state . VideoStream . VideoRangeType is VideoRangeType . HDR10 or VideoRangeType . HLG or VideoRangeType . HDR10Plus ;
& & state . VideoStream . VideoRangeType is VideoRangeType . HDR10 or VideoRangeType . HLG or VideoRangeType . HDR10Plus ;
@ -4976,12 +4985,6 @@ namespace MediaBrowser.Controller.MediaEncoding
return swFilterChain ;
return swFilterChain ;
}
}
if ( _mediaEncoder . EncoderVersion . CompareTo ( new Version ( "5.0.0" ) ) < 0 )
{
// All features used here requires ffmpeg 5.0 or later, fallback to software filters if using an old ffmpeg
return swFilterChain ;
}
var doDeintH264 = state . DeInterlace ( "h264" , true ) | | state . DeInterlace ( "avc" , true ) ;
var doDeintH264 = state . DeInterlace ( "h264" , true ) | | state . DeInterlace ( "avc" , true ) ;
var doDeintHevc = state . DeInterlace ( "h265" , true ) | | state . DeInterlace ( "hevc" , true ) ;
var doDeintHevc = state . DeInterlace ( "h265" , true ) | | state . DeInterlace ( "hevc" , true ) ;
var doDeintH2645 = doDeintH264 | | doDeintHevc ;
var doDeintH2645 = doDeintH264 | | doDeintHevc ;
@ -4991,52 +4994,110 @@ namespace MediaBrowser.Controller.MediaEncoding
var reqH = state . BaseRequest . Height ;
var reqH = state . BaseRequest . Height ;
var reqMaxW = state . BaseRequest . MaxWidth ;
var reqMaxW = state . BaseRequest . MaxWidth ;
var reqMaxH = state . BaseRequest . MaxHeight ;
var reqMaxH = state . BaseRequest . MaxHeight ;
var threeDFormat = state . MediaSource . Video3DFormat ;
var mainFilters = new List < string > ( ) ;
var newfilters = new List < string > ( ) ;
var hasSubs = state . SubtitleStream is not null & & state . SubtitleDeliveryMethod = = SubtitleDeliveryMethod . Encode ;
var noOverlay = swFilterChain . OverlayFilters . Count = = 0 ;
var hasTextSubs = hasSubs & & state . SubtitleStream . IsTextSubtitleStream ;
var supportsHwDeint = _mediaEncoder . SupportsFilter ( "yadif_videotoolbox" ) ;
var hasGraphicalSubs = hasSubs & & ! state . SubtitleStream . IsTextSubtitleStream ;
var supportsHwScale = _mediaEncoder . SupportsFilter ( "scale_vt" ) ;
var hasAssSubs = hasSubs
// VideoToolbox is special. It does not use a separate tone mapping filter like others. Instead, it performs both tone mapping and scaling in a single filter.
& & ( string . Equals ( state . SubtitleStream . Codec , "ass" , StringComparison . OrdinalIgnoreCase )
var useHwToneMapping = IsVideoToolboxTonemapAvailable ( state , options ) & & supportsHwScale ;
| | string . Equals ( state . SubtitleStream . Codec , "ssa" , StringComparison . OrdinalIgnoreCase ) ) ;
// fallback to software filters if we are using filters not supported by hardware yet.
// VideoToolbox is special. It does not use a separate tone mapping filter like others.
var useHardwareFilters = noOverlay & & ( ! doDeintH2645 | | supportsHwDeint | | supportsHwScale ) ;
// Instead, it performs both tone mapping and scaling in a single filter.
var useVtToneMapping = IsVideoToolboxTonemapAvailable ( state , options ) ;
// Use OpenCL tone mapping as a fallback
var useOclToneMapping = ! useVtToneMapping & & IsHwTonemapAvailable ( state , options ) ;
// Fallback to software filters if we are using filters not supported by hardware yet.
// OpenCL won't work without proper VT support as the hwmap interop required will not be present there
var useHardwareFilters = IsVideoToolBoxFullSupported ( ) ;
if ( ! useHardwareFilters )
if ( ! useHardwareFilters )
{
{
return swFilterChain ;
return swFilterChain ;
}
}
if ( ! supportsHwScale )
if ( ! ( useVtToneMapping | | useOclToneMapping | | hasSubs | | doDeintH2645 ) )
{
{
var swScaleFilter = GetSwScaleFilter ( state , options , vidEncoder , inW , inH , threeDFormat , reqW , reqH , reqMaxW , reqMaxH ) ;
// Dummy action to return empty filters when nothing to do.
newfilters . Add ( swScaleFilter ) ;
return ( mainFilters , mainFilters , mainFilters ) ;
}
// Override the color when doing OpenCL Tone mapping, where we are using hardware surface output.
if ( useOclToneMapping )
{
mainFilters . Add ( GetOverwriteColorPropertiesParam ( state , true ) ) ;
}
}
// With OpenCL tone mapping, we are using native hwmap and there's no need to specify a format for the main stream.
// However, for other cases, we have to specify the format for the main stream when we are doing subtitle burn-in.
// This is because the default upload option is not always processable with VideoToolbox.
// Most notably, yuv420p should be replaced by nv12.
else if ( hasSubs )
{
var is8Bit = string . Equals ( "yuv420p" , state . VideoStream . PixelFormat , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( "yuvj420p" , state . VideoStream . PixelFormat , StringComparison . OrdinalIgnoreCase ) ;
var is10Bit = string . Equals ( "yuv420p10le" , state . VideoStream . PixelFormat , StringComparison . OrdinalIgnoreCase ) ;
// hwupload on videotoolbox encoders can automatically convert AVFrame into its CVPixelBuffer equivalent
if ( is8Bit )
// videotoolbox will automatically convert the CVPixelBuffer to a pixel format the encoder supports, so we don't have to set a pixel format explicitly here
// This will reduce CPU usage significantly on UHD videos with 10 bit colors because we bypassed the ffmpeg pixel format conversion
newfilters . Add ( "hwupload" ) ;
if ( supportsHwScale )
{
{
mainFilters . Add ( "format=nv12" ) ;
} else if ( is10Bit )
{
mainFilters . Add ( "format=p010" ) ;
}
}
mainFilters . Add ( "hwupload" ) ;
var hwScaleFilter = GetHwScaleFilter ( "vt" , null , inW , inH , reqW , reqH , reqMaxW , reqMaxH ) ;
var hwScaleFilter = GetHwScaleFilter ( "vt" , null , inW , inH , reqW , reqH , reqMaxW , reqMaxH ) ;
if ( useHwToneMapping )
if ( useVt ToneMapping)
{
{
var tonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709" ;
const string T onemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709" ;
hwScaleFilter = string . IsNullOrEmpty ( hwScaleFilter )
hwScaleFilter = string . IsNullOrEmpty ( hwScaleFilter )
? "scale_vt=" + tonemapArgs
? "scale_vt=" + T onemapArgs
: hwScaleFilter + ":" + tonemapArgs ;
: hwScaleFilter + ":" + T onemapArgs;
}
}
newfilters . Add ( hwScaleFilter ) ;
mainFilters . Add ( hwScaleFilter ) ;
if ( useOclToneMapping )
{
mainFilters . Add ( "hwmap=derive_device=opencl" ) ;
mainFilters . Add ( GetHwTonemapFilter ( options , "opencl" , "nv12" ) ) ;
mainFilters . Add ( "hwmap=derive_device=videotoolbox:reverse=1" ) ;
}
}
if ( doDeintH2645 )
if ( doDeintH2645 )
{
{
var deintFilter = GetHwDeinterlaceFilter ( state , options , "videotoolbox" ) ;
var deintFilter = GetHwDeinterlaceFilter ( state , options , "videotoolbox" ) ;
newfilters . Add ( deintFilter ) ;
mainF ilters. Add ( deintFilter ) ;
}
}
return ( newfilters , swFilterChain . SubFilters , swFilterChain . OverlayFilters ) ;
var subFilters = new List < string > ( ) ;
var overlayFilters = new List < string > ( ) ;
if ( hasSubs )
{
if ( hasGraphicalSubs )
{
var subPreProcFilters = GetGraphicalSubPreProcessFilters ( inW , inH , reqW , reqH , reqMaxW , reqMaxH ) ;
subFilters . Add ( subPreProcFilters ) ;
subFilters . Add ( "format=bgra" ) ;
}
else if ( hasTextSubs )
{
var framerate = state . VideoStream ? . RealFrameRate ;
var subFramerate = hasAssSubs ? Math . Min ( framerate ? ? 25 , 60 ) : 10 ;
var alphaSrcFilter = GetAlphaSrcFilter ( state , inW , inH , reqW , reqH , reqMaxW , reqMaxH , subFramerate ) ;
var subTextSubtitlesFilter = GetTextSubtitlesFilter ( state , true , true ) ;
subFilters . Add ( alphaSrcFilter ) ;
subFilters . Add ( "format=bgra" ) ;
subFilters . Add ( subTextSubtitlesFilter ) ;
}
subFilters . Add ( "hwupload=derive_device=videotoolbox" ) ;
overlayFilters . Add ( "overlay_videotoolbox=eof_action=pass:repeatlast=0" ) ;
}
return ( mainFilters , subFilters , overlayFilters ) ;
}
}
/// <summary>
/// <summary>
@ -6028,22 +6089,28 @@ namespace MediaBrowser.Controller.MediaEncoding
| | string . Equals ( "yuvj420p" , videoStream . PixelFormat , StringComparison . OrdinalIgnoreCase ) ;
| | string . Equals ( "yuvj420p" , videoStream . PixelFormat , StringComparison . OrdinalIgnoreCase ) ;
var is8_10bitSwFormatsVt = is8bitSwFormatsVt | | string . Equals ( "yuv420p10le" , videoStream . PixelFormat , StringComparison . OrdinalIgnoreCase ) ;
var is8_10bitSwFormatsVt = is8bitSwFormatsVt | | string . Equals ( "yuv420p10le" , videoStream . PixelFormat , StringComparison . OrdinalIgnoreCase ) ;
// Hardware surface only make sense when interop with OpenCL
// VideoToolbox's Hardware surface in ffmpeg is not only slower than hwupload, but also breaks HDR in many cases.
// For example: https://trac.ffmpeg.org/ticket/10884
var useOclToneMapping = ! IsVideoToolboxTonemapAvailable ( state , options ) & & IsHwTonemapAvailable ( state , options ) ;
var useHwSurface = useOclToneMapping & & IsVideoToolBoxFullSupported ( ) & & _mediaEncoder . SupportsFilter ( "alphasrc" ) ;
if ( is8bitSwFormatsVt )
if ( is8bitSwFormatsVt )
{
{
if ( string . Equals ( "avc" , videoStream . Codec , StringComparison . OrdinalIgnoreCase )
if ( string . Equals ( "avc" , videoStream . Codec , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( "h264" , videoStream . Codec , StringComparison . OrdinalIgnoreCase ) )
| | string . Equals ( "h264" , videoStream . Codec , StringComparison . OrdinalIgnoreCase ) )
{
{
return GetHwaccelType ( state , options , "h264" , bitDepth , false ) ;
return GetHwaccelType ( state , options , "h264" , bitDepth , useHwSurfac e) ;
}
}
if ( string . Equals ( "mpeg2video" , videoStream . Codec , StringComparison . OrdinalIgnoreCase ) )
if ( string . Equals ( "mpeg2video" , videoStream . Codec , StringComparison . OrdinalIgnoreCase ) )
{
{
return GetHwaccelType ( state , options , "mpeg2video" , bitDepth , fals e) ;
return GetHwaccelType ( state , options , "mpeg2video" , bitDepth , useHwSurfac e) ;
}
}
if ( string . Equals ( "mpeg4" , videoStream . Codec , StringComparison . OrdinalIgnoreCase ) )
if ( string . Equals ( "mpeg4" , videoStream . Codec , StringComparison . OrdinalIgnoreCase ) )
{
{
return GetHwaccelType ( state , options , "mpeg4" , bitDepth , fals e) ;
return GetHwaccelType ( state , options , "mpeg4" , bitDepth , useHwSurfac e) ;
}
}
}
}
@ -6052,12 +6119,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if ( string . Equals ( "hevc" , videoStream . Codec , StringComparison . OrdinalIgnoreCase )
if ( string . Equals ( "hevc" , videoStream . Codec , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( "h265" , videoStream . Codec , StringComparison . OrdinalIgnoreCase ) )
| | string . Equals ( "h265" , videoStream . Codec , StringComparison . OrdinalIgnoreCase ) )
{
{
return GetHwaccelType ( state , options , "hevc" , bitDepth , fals e) ;
return GetHwaccelType ( state , options , "hevc" , bitDepth , useHwSurfac e) ;
}
}
if ( string . Equals ( "vp9" , videoStream . Codec , StringComparison . OrdinalIgnoreCase ) )
if ( string . Equals ( "vp9" , videoStream . Codec , StringComparison . OrdinalIgnoreCase ) )
{
{
return GetHwaccelType ( state , options , "vp9" , bitDepth , fals e) ;
return GetHwaccelType ( state , options , "vp9" , bitDepth , useHwSurfac e) ;
}
}
}
}