@ -460,16 +460,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if ( state . IsVideoRequest
& & string . Equals ( encodingOptions . HardwareAccelerationType , "vaapi" , StringComparison . OrdinalIgnoreCase ) )
{
var hasGraphicalSubs = state . SubtitleStream ! = null & & ! state . SubtitleStream . IsTextSubtitleStream & & state . SubtitleDeliveryMethod = = SubtitleDeliveryMethod . Encode ;
var hwOutputFormat = "vaapi" ;
if ( hasGraphicalSubs )
{
hwOutputFormat = "yuv420p" ;
}
arg . Append ( "-hwaccel vaapi -hwaccel_output_format " )
. Append ( hwOutputFormat )
arg . Append ( "-hwaccel vaapi -hwaccel_output_format vaapi" )
. Append ( " -vaapi_device " )
. Append ( encodingOptions . VaapiDevice )
. Append ( ' ' ) ;
@ -480,20 +471,26 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var videoDecoder = GetHardwareAcceleratedVideoDecoder ( state , encodingOptions ) ;
var outputVideoCodec = GetVideoEncoder ( state , encodingOptions ) ;
var hasTextSubs = state . SubtitleStream ! = null & & state . SubtitleStream . IsTextSubtitleStream & & state . SubtitleDeliveryMethod = = SubtitleDeliveryMethod . Encode ;
if ( encodingOptions . EnableHardwareEncoding & & outputVideoCodec . Contains ( "qsv" , StringComparison . OrdinalIgnoreCase ) )
if ( ! hasTextSubs )
{
if ( ! string . IsNullOrEmpty ( videoDecoder ) & & videoDecoder . Contains ( "qsv" , StringComparison . OrdinalIgnoreCase ) )
{
arg . Append ( "-hwaccel qsv " ) ;
}
else
// While using QSV encoder
if ( ( outputVideoCodec ? ? string . Empty ) . IndexOf ( "qsv" , StringComparison . OrdinalIgnoreCase ) ! = - 1 )
{
arg . Append ( "-init_hw_device qsv=hw -filter_hw_device hw " ) ;
// While using QSV decoder
if ( ( videoDecoder ? ? string . Empty ) . IndexOf ( "qsv" , StringComparison . OrdinalIgnoreCase ) ! = - 1 )
{
arg . Append ( "-hwaccel qsv " ) ;
}
// While using SW decoder
else
{
arg . Append ( "-init_hw_device qsv=hw -filter_hw_device hw " ) ;
}
}
}
arg . Append ( videoDecoder + " " ) ;
}
arg . Append ( "-i " )
@ -503,17 +500,6 @@ namespace MediaBrowser.Controller.MediaEncoding
& & state . SubtitleDeliveryMethod = = SubtitleDeliveryMethod . Encode
& & state . SubtitleStream . IsExternal & & ! state . SubtitleStream . IsTextSubtitleStream )
{
if ( state . VideoStream ! = null & & state . VideoStream . Width . HasValue )
{
// This is hacky but not sure how to get the exact subtitle resolution
int height = Convert . ToInt32 ( state . VideoStream . Width . Value / 16.0 * 9.0 ) ;
arg . Append ( " -canvas_size " )
. Append ( state . VideoStream . Width . Value . ToString ( CultureInfo . InvariantCulture ) )
. Append ( ':' )
. Append ( height . ToString ( CultureInfo . InvariantCulture ) ) ;
}
var subtitlePath = state . SubtitleStream . Path ;
if ( string . Equals ( Path . GetExtension ( subtitlePath ) , ".sub" , StringComparison . OrdinalIgnoreCase ) )
@ -1546,9 +1532,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
/// Gets the internal graphical subtitle param.
/// Gets the graphical subtitle param.
/// </summary>
public string GetGraphicalSubtitleParam ( EncodingJobInfo state , EncodingOptions options , string outputVideoCodec )
public string GetGraphicalSubtitleParam (
EncodingJobInfo state ,
EncodingOptions options ,
string outputVideoCodec )
{
var outputSizeParam = string . Empty ;
@ -1562,54 +1551,77 @@ namespace MediaBrowser.Controller.MediaEncoding
{
outputSizeParam = GetOutputSizeParam ( state , options , outputVideoCodec ) . TrimEnd ( '"' ) ;
if ( string . Equals ( outputVideoCodec , "h264_vaapi" , StringComparison . OrdinalIgnoreCase ) )
var index = outputSizeParam . IndexOf ( "hwdownload" , StringComparison . OrdinalIgnoreCase ) ;
if ( index ! = - 1 )
{
var index = outputSizeParam . IndexOf ( "format" , StringComparison . OrdinalIgnoreCase ) ;
if ( index ! = - 1 )
{
outputSizeParam = "," + outputSizeParam . Substring ( index ) ;
}
outputSizeParam = "," + outputSizeParam . Substring ( index ) ;
}
else
{
var index = outputSizeParam . IndexOf ( " scale ", StringComparison . OrdinalIgnoreCase ) ;
index = outputSizeParam . IndexOf ( "format" , StringComparison . OrdinalIgnoreCase ) ;
if ( index ! = - 1 )
{
outputSizeParam = "," + outputSizeParam . Substring ( index ) ;
}
}
}
if ( string . Equals ( outputVideoCodec , "h264_vaapi" , StringComparison . OrdinalIgnoreCase )
& & outputSizeParam . Length = = 0 )
{
outputSizeParam = ",format=nv12|vaapi,hwupload" ;
// Add parameters to use VAAPI with burn-in subttiles (GH issue #642)
if ( state . SubtitleStream ! = null
& & state . SubtitleStream . IsTextSubtitleStream
& & state . SubtitleDeliveryMethod = = SubtitleDeliveryMethod . Encode )
{
outputSizeParam + = ",hwmap=mode=read+write+direct" ;
else
{
index = outputSizeParam . IndexOf ( "yadif" , StringComparison . OrdinalIgnoreCase ) ;
if ( index ! = - 1 )
{
outputSizeParam = "," + outputSizeParam . Substring ( index ) ;
}
else
{
index = outputSizeParam . IndexOf ( "scale" , StringComparison . OrdinalIgnoreCase ) ;
if ( index ! = - 1 )
{
outputSizeParam = "," + outputSizeParam . Substring ( index ) ;
}
}
}
}
}
var videoSizeParam = string . Empty ;
var videoDecoder = GetHardwareAcceleratedVideoDecoder ( state , options ) ;
// Setup subtitle scaling
if ( state . VideoStream ! = null & & state . VideoStream . Width . HasValue & & state . VideoStream . Height . HasValue )
{
// force_original_aspect_ratio=decrease
// Enable decreasing output video width or height if necessary to keep the original aspect ratio
videoSizeParam = string . Format (
CultureInfo . InvariantCulture ,
"scale={0}:{1} ",
"scale={0}:{1} :force_original_aspect_ratio=decrease ",
state . VideoStream . Width . Value ,
state . VideoStream . Height . Value ) ;
// For QSV, feed it into hardware encoder now
// For QSV, feed it into hardware encoder now
if ( string . Equals ( outputVideoCodec , "h264_qsv" , StringComparison . OrdinalIgnoreCase ) )
{
videoSizeParam + = ",hwupload=extra_hw_frames=64" ;
}
// For VAAPI and CUVID decoder
// these encoders cannot automatically adjust the size of graphical subtitles to fit the output video,
// thus needs to be manually adjusted.
if ( string . Equals ( options . HardwareAccelerationType , "vaapi" , StringComparison . OrdinalIgnoreCase )
| | ( videoDecoder ? ? string . Empty ) . IndexOf ( "cuvid" , StringComparison . OrdinalIgnoreCase ) ! = - 1 )
{
var videoStream = state . VideoStream ;
var inputWidth = videoStream ? . Width ;
var inputHeight = videoStream ? . Height ;
var ( width , height ) = GetFixedOutputSize ( inputWidth , inputHeight , request . Width , request . Height , request . MaxWidth , request . MaxHeight ) ;
if ( width . HasValue & & height . HasValue )
{
videoSizeParam = string . Format (
CultureInfo . InvariantCulture ,
"scale={0}:{1}:force_original_aspect_ratio=decrease" ,
width . Value ,
height . Value ) ;
}
}
}
var mapPrefix = state . SubtitleStream . IsExternal ?
@ -1620,12 +1632,34 @@ namespace MediaBrowser.Controller.MediaEncoding
? 0
: state . SubtitleStream . Index ;
var videoDecoder = GetHardwareAcceleratedVideoDecoder ( state , options ) ;
// Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference)
var retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"" ;
if ( string . Equals ( outputVideoCodec , "h264_qsv" , StringComparison . OrdinalIgnoreCase ) )
// When the input may or may not be hardware VAAPI decodable
if ( string . Equals ( options . HardwareAccelerationType , "vaapi" , StringComparison . OrdinalIgnoreCase ) & & options . EnableHardwareEncoding )
{
/ *
[base] : HW scaling video to OutputSize
[sub] : SW scaling subtitle to FixedOutputSize
[base] [ sub ] : SW overlay
* /
outputSizeParam = outputSizeParam . TrimStart ( ',' ) ;
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"" ;
}
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
else if ( string . Equals ( options . HardwareAccelerationType , "vaapi" , StringComparison . OrdinalIgnoreCase ) & & ! options . EnableHardwareEncoding )
{
/ *
[base] : SW scaling video to OutputSize
[sub] : SW scaling subtitle to FixedOutputSize
[base] [ sub ] : SW overlay
* /
outputSizeParam = outputSizeParam . TrimStart ( ',' ) ;
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"" ;
}
else if ( string . Equals ( outputVideoCodec , "h264_qsv" , StringComparison . OrdinalIgnoreCase ) )
{
/ *
QSV in FFMpeg can now setup hardware overlay for transcodes .
@ -1689,7 +1723,8 @@ namespace MediaBrowser.Controller.MediaEncoding
return ( Convert . ToInt32 ( outputWidth ) , Convert . ToInt32 ( outputHeight ) ) ;
}
public List < string > GetScalingFilters ( int? videoWidth ,
public List < string > GetScalingFilters ( EncodingJobInfo state ,
int? videoWidth ,
int? videoHeight ,
Video3DFormat ? threedFormat ,
string videoDecoder ,
@ -1708,7 +1743,9 @@ namespace MediaBrowser.Controller.MediaEncoding
requestedMaxWidth ,
requestedMaxHeight ) ;
if ( ( string . Equals ( videoEncoder , "h264_vaapi" , StringComparison . OrdinalIgnoreCase ) | | string . Equals ( videoEncoder , "h264_qsv" , StringComparison . OrdinalIgnoreCase ) )
var hasTextSubs = state . SubtitleStream ! = null & & state . SubtitleStream . IsTextSubtitleStream & & state . SubtitleDeliveryMethod = = SubtitleDeliveryMethod . Encode ;
if ( string . Equals ( videoEncoder , "h264_vaapi" , StringComparison . OrdinalIgnoreCase ) | | ( string . Equals ( videoEncoder , "h264_qsv" , StringComparison . OrdinalIgnoreCase ) & & ! hasTextSubs )
& & width . HasValue
& & height . HasValue )
{
@ -1738,7 +1775,7 @@ namespace MediaBrowser.Controller.MediaEncoding
filters . Add ( string . Format ( CultureInfo . InvariantCulture , "scale_{0}=format=nv12" , vaapi_or_qsv ) ) ;
}
}
else if ( ( videoDecoder ? ? string . Empty ) . IndexOf ( " _ cuvid", StringComparison . OrdinalIgnoreCase ) ! = - 1
else if ( ( videoDecoder ? ? string . Empty ) . IndexOf ( " cuvid", StringComparison . OrdinalIgnoreCase ) ! = - 1
& & width . HasValue
& & height . HasValue )
{
@ -1942,8 +1979,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetOutputSizeParam (
EncodingJobInfo state ,
EncodingOptions options ,
string outputVideoCodec ,
bool allowTimeStampCopy = true )
string outputVideoCodec )
{
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
@ -1952,42 +1988,57 @@ namespace MediaBrowser.Controller.MediaEncoding
var videoStream = state . VideoStream ;
var filters = new List < string > ( ) ;
var videoDecoder = GetHardwareAcceleratedVideoDecoder ( state , options ) ;
var inputWidth = videoStream ? . Width ;
var inputHeight = videoStream ? . Height ;
var threeDFormat = state . MediaSource . Video3DFormat ;
var hasTextSubs = state . SubtitleStream ! = null & & state . SubtitleStream . IsTextSubtitleStream & & state . SubtitleDeliveryMethod = = SubtitleDeliveryMethod . Encode ;
// When the input may or may not be hardware VAAPI decodable
if ( string . Equals ( options . HardwareAccelerationType , "vaapi" , StringComparison . OrdinalIgnoreCase ) & & options . EnableHardwareEncoding )
{
filters . Add ( "format=nv12|vaapi" ) ;
filters . Add ( "hwupload" ) ;
}
// When the input may or may not be hardware QSV decodable
else if ( string . Equals ( options . HardwareAccelerationType , "qsv" , StringComparison . OrdinalIgnoreCase ) & & options . EnableHardwareEncoding )
{
if ( ! hasTextSubs )
{
filters . Add ( "format=nv12|qsv" ) ;
filters . Add ( "hwupload=extra_hw_frames=64" ) ;
}
}
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
var hwType = options . HardwareAccelerationType ? ? string . Empty ;
if ( string . Equals ( hwType , "vaapi" , StringComparison . OrdinalIgnoreCase ) & & ! options . EnableHardwareEncoding )
else if ( string . Equals ( options. HardwareAcceleration Type, "vaapi" , StringComparison . OrdinalIgnoreCase ) & & ! options . EnableHardwareEncoding )
{
filters . Add ( "hwdownload" ) ;
var codec = videoStream . Codec . ToLowerInvariant ( ) ;
var pixelFormat = videoStream . PixelFormat . ToLowerInvariant ( ) ;
// If transcoding from 10 bit, transform colour spaces too
if ( ! string . IsNullOrEmpty ( videoStream . PixelFormat )
& & videoStream . PixelFormat . IndexOf ( "p10" , StringComparison . OrdinalIgnoreCase ) ! = - 1
& & string . Equals ( outputVideoCodec , "libx264" , StringComparison . OrdinalIgnoreCase ) )
// Assert 10-bit hardware VAAPI decodable
if ( ( pixelFormat ? ? string . Empty ) . IndexOf ( "p10" , StringComparison . OrdinalIgnoreCase ) ! = - 1
& & ( string . Equals ( codec , "hevc" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( codec , "h265" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( codec , "vp9" , StringComparison . OrdinalIgnoreCase ) ) )
{
filters . Add ( "hwdownload" ) ;
filters . Add ( "format=p010le" ) ;
filters . Add ( "format=nv12" ) ;
}
else
// Assert 8-bit hardware VAAPI decodable
else if ( ( pixelFormat ? ? string . Empty ) . IndexOf ( "p10" , StringComparison . OrdinalIgnoreCase ) = = - 1 )
{
filters . Add ( "hwdownload" ) ;
filters . Add ( "format=nv12" ) ;
}
}
if ( string . Equals ( outputVideoCodec , "h264_vaapi" , StringComparison . OrdinalIgnoreCase ) )
{
filters . Add ( "format=nv12|vaapi" ) ;
filters . Add ( "hwupload" ) ;
}
var videoDecoder = GetHardwareAcceleratedVideoDecoder ( state , options ) ;
// If we are software decoding, and hardware encoding
if ( string . Equals ( outputVideoCodec , "h264_qsv" , StringComparison . OrdinalIgnoreCase )
& & ( string . IsNullOrEmpty ( videoDecoder ) | | ! videoDecoder . Contains ( "qsv" , StringComparison . OrdinalIgnoreCase ) ) )
{
filters . Add ( "format=nv12|qsv" ) ;
filters . Add ( "hwupload=extra_hw_frames=64" ) ;
}
// Add hardware deinterlace filter before scaling filter
if ( state . DeInterlace ( "h264" , true ) )
{
if ( string . Equals ( outputVideoCodec , "h264_vaapi" , StringComparison . OrdinalIgnoreCase ) )
@ -1996,17 +2047,23 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if ( string . Equals ( outputVideoCodec , "h264_qsv" , StringComparison . OrdinalIgnoreCase ) )
{
filters . Add ( string . Format ( CultureInfo . InvariantCulture , "deinterlace_qsv" ) ) ;
if ( ! hasTextSubs )
{
filters . Add ( string . Format ( CultureInfo . InvariantCulture , "deinterlace_qsv" ) ) ;
}
}
}
if ( ( state . DeInterlace ( "h264" , true ) | | state . DeInterlace ( "h265" , true ) | | state . DeInterlace ( "hevc" , true ) )
& & ! string . Equals ( outputVideoCodec , "h264_vaapi" , StringComparison . OrdinalIgnoreCase ) )
// Add software deinterlace filter before scaling filter
if ( ( ( state . DeInterlace ( "h264" , true ) | | state . DeInterlace ( "h265" , true ) | | state . DeInterlace ( "hevc" , true ) )
& & ! string . Equals ( outputVideoCodec , "h264_vaapi" , StringComparison . OrdinalIgnoreCase )
& & ! string . Equals ( outputVideoCodec , "h264_qsv" , StringComparison . OrdinalIgnoreCase ) )
| | ( hasTextSubs & & state . DeInterlace ( "h264" , true ) & & string . Equals ( outputVideoCodec , "h264_qsv" , StringComparison . OrdinalIgnoreCase ) ) )
{
var inputFramerate = videoStream ? . RealFrameRate ;
// If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle
if ( string . Equals ( options . DeinterlaceMethod , " bobandweave ", StringComparison . OrdinalIgnoreCase ) & & ( inputFramerate ? ? 60 ) < = 30 )
if ( string . Equals ( options . DeinterlaceMethod , " yadif_ bob", StringComparison . OrdinalIgnoreCase ) & & ( inputFramerate ? ? 60 ) < = 30 )
{
filters . Add ( "yadif=1:-1:0" ) ;
}
@ -2016,11 +2073,21 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
var inputWidth = videoStream ? . Width ;
var inputHeight = videoStream ? . Height ;
var threeDFormat = state . MediaSource . Video3DFormat ;
// Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr
filters . AddRange ( GetScalingFilters ( state , inputWidth , inputHeight , threeDFormat , videoDecoder , outputVideoCodec , request . Width , request . Height , request . MaxWidth , request . MaxHeight ) ) ;
filters . AddRange ( GetScalingFilters ( inputWidth , inputHeight , threeDFormat , videoDecoder , outputVideoCodec , request . Width , request . Height , request . MaxWidth , request . MaxHeight ) ) ;
// Add parameters to use VAAPI with burn-in text subttiles (GH issue #642)
if ( string . Equals ( options . HardwareAccelerationType , "vaapi" , StringComparison . OrdinalIgnoreCase ) & & options . EnableHardwareEncoding )
{
if ( state . SubtitleStream ! = null
& & state . SubtitleStream . IsTextSubtitleStream
& & state . SubtitleDeliveryMethod = = SubtitleDeliveryMethod . Encode )
{
// Test passed on Intel and AMD gfx
filters . Add ( "hwmap=mode=read+write" ) ;
filters . Add ( "format=nv12" ) ;
}
}
var output = string . Empty ;
@ -2038,11 +2105,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
filters . Add ( "hwmap" ) ;
}
if ( allowTimeStampCopy )
{
output + = " -copyts" ;
}
}
if ( filters . Count > 0 )
@ -2219,7 +2281,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
inputModifier + = " " + videoDecoder ;
if ( ( videoDecoder ? ? string . Empty ) . IndexOf ( " _ cuvid", StringComparison . OrdinalIgnoreCase ) ! = - 1 )
if ( ( videoDecoder ? ? string . Empty ) . IndexOf ( " cuvid", StringComparison . OrdinalIgnoreCase ) ! = - 1 )
{
var videoStream = state . VideoStream ;
var inputWidth = videoStream ? . Width ;
@ -2228,7 +2290,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var ( width , height ) = GetFixedOutputSize ( inputWidth , inputHeight , request . Width , request . Height , request . MaxWidth , request . MaxHeight ) ;
if ( ( videoDecoder ? ? string . Empty ) . IndexOf ( " _ cuvid", StringComparison . OrdinalIgnoreCase ) ! = - 1
if ( ( videoDecoder ? ? string . Empty ) . IndexOf ( " cuvid", StringComparison . OrdinalIgnoreCase ) ! = - 1
& & width . HasValue
& & height . HasValue )
{
@ -2526,6 +2588,12 @@ namespace MediaBrowser.Controller.MediaEncoding
case "h264" :
if ( _mediaEncoder . SupportsDecoder ( "h264_cuvid" ) & & encodingOptions . HardwareDecodingCodecs . Contains ( "h264" , StringComparer . OrdinalIgnoreCase ) )
{
// cuvid decoder does not support 10-bit input
if ( ( videoStream . BitDepth ? ? 8 ) > 8 )
{
encodingOptions . HardwareDecodingCodecs = Array . Empty < string > ( ) ;
return null ;
}
return "-c:v h264_cuvid " ;
}
break ;
@ -2773,14 +2841,27 @@ namespace MediaBrowser.Controller.MediaEncoding
var hasGraphicalSubs = state . SubtitleStream ! = null & & ! state . SubtitleStream . IsTextSubtitleStream & & state . SubtitleDeliveryMethod = = SubtitleDeliveryMethod . Encode ;
var hasCopyTs = false ;
// Add resolution params, if specified
if ( ! hasGraphicalSubs )
{
var outputSizeParam = GetOutputSizeParam ( state , encodingOptions , videoCodec ) ;
args + = outputSizeParam ;
hasCopyTs = outputSizeParam . IndexOf ( "copyts" , StringComparison . OrdinalIgnoreCase ) ! = - 1 ;
}
// This is for graphical subs
if ( hasGraphicalSubs )
{
var graphicalSubtitleParam = GetGraphicalSubtitleParam ( state , encodingOptions , videoCodec ) ;
args + = graphicalSubtitleParam ;
hasCopyTs = graphicalSubtitleParam . IndexOf ( "copyts" , StringComparison . OrdinalIgnoreCase ) ! = - 1 ;
}
if ( state . RunTimeTicks . HasValue & & state . BaseRequest . CopyTimestamps )
{
if ( ! hasCopyTs )
@ -2788,13 +2869,12 @@ namespace MediaBrowser.Controller.MediaEncoding
args + = " -copyts" ;
}
args + = " -avoid_negative_ts disabled -start_at_zero" ;
}
args + = " -avoid_negative_ts disabled" ;
// This is for internal graphical subs
if ( hasGraphicalSubs )
{
args + = GetGraphicalSubtitleParam ( state , encodingOptions , videoCodec ) ;
if ( ! ( state . SubtitleStream ! = null & & state . SubtitleStream . IsExternal & & ! state . SubtitleStream . IsTextSubtitleStream ) )
{
args + = " -start_at_zero" ;
}
}
var qualityParam = GetVideoQualityParam ( state , videoCodec , encodingOptions , defaultPreset ) ;
@ -2900,6 +2980,5 @@ namespace MediaBrowser.Controller.MediaEncoding
string . Empty ,
string . Empty ) . Trim ( ) ;
}
}
}