@ -665,7 +665,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
@@ -665,7 +665,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
string vaapiDevice ,
Option < int > qsvExtraHardwareFrames )
{
FFmpegPlaybackSettings playbackSettings = FFmpegPlaybackSettingsCalculator . CalculateError Settings (
FFmpegPlaybackSettings playbackSettings = FFmpegPlaybackSettingsCalculator . CalculateGeneratedImage Settings (
channel . StreamingMode ,
channel . FFmpegProfile ,
hlsRealtime ) ;
@ -726,57 +726,6 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
@@ -726,57 +726,6 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
playbackSettings . NormalizeColors ,
playbackSettings . Deinterlace ) ;
OutputFormatKind outputFormat = OutputFormatKind . MpegTs ;
switch ( channel . StreamingMode )
{
case StreamingMode . HttpLiveStreamingSegmenter :
outputFormat = OutputFormatKind . Hls ;
break ;
}
Option < string > hlsPlaylistPath = outputFormat is OutputFormatKind . Hls or OutputFormatKind . HlsMp4
? Path . Combine ( FileSystemLayout . TranscodeFolder , channel . Number , "live.m3u8" )
: Option < string > . None ;
long nowSeconds = now . ToUnixTimeSeconds ( ) ;
Option < string > hlsSegmentTemplate = outputFormat switch
{
OutputFormatKind . Hls = > Path . Combine ( FileSystemLayout . TranscodeFolder , channel . Number , "live%06d.ts" ) ,
OutputFormatKind . HlsMp4 = > Path . Combine (
FileSystemLayout . TranscodeFolder ,
channel . Number ,
$"live_{nowSeconds}_%06d.m4s" ) ,
_ = > Option < string > . None
} ;
Option < string > hlsInitTemplate = outputFormat switch
{
OutputFormatKind . HlsMp4 = > $"{nowSeconds}_init.mp4" ,
_ = > Option < string > . None
} ;
Option < string > hlsSegmentOptions = Option < string > . None ;
if ( outputFormat is OutputFormatKind . Hls )
{
string options = string . Empty ;
if ( ptsOffset = = TimeSpan . Zero )
{
options + = "+initial_discontinuity" ;
}
if ( audioFormat = = AudioFormat . AacLatm )
{
options + = "+latm" ;
}
if ( ! string . IsNullOrWhiteSpace ( options ) )
{
hlsSegmentOptions = $"mpegts_flags={options}" ;
}
}
string videoPath = _l ocalFileSystem . GetCustomOrDefaultFile (
FileSystemLayout . ResourcesCacheFolder ,
"background.png" ) ;
@ -802,6 +751,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
@@ -802,6 +751,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode ( playbackSettings ) ;
_l ogger . LogDebug ( "HW accel mode: {HwAccel}" , hwAccel ) ;
var hlsOptions = GetHlsOptions ( channel , now , ptsOffset , audioFormat ) ;
var ffmpegState = new FFmpegState (
channel . Number = = FileSystemLayout . TranscodeTroubleshootingChannel ,
HardwareAccelerationMode . None , // no hw accel decode since errors loop
@ -816,11 +767,11 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
@@ -816,11 +767,11 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
None ,
None ,
None ,
outputFormat ,
hlsPlaylistPath ,
hlsSegmentTemplate ,
hlsInitTemplate ,
hlsSegmentOptions ,
hlsOpti ons . O utputFormat,
hlsOptions . Hls PlaylistPath ,
hlsOptions . Hls SegmentTemplate ,
hlsOptions . Hls InitTemplate ,
hlsOptions . Hls SegmentOptions ,
ptsOffset ,
Option < int > . None ,
qsvExtraHardwareFrames ,
@ -862,6 +813,136 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
@@ -862,6 +813,136 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
return GetCommand ( ffmpegPath , videoInputFile , audioInputFile , None , None , None , pipeline ) ;
}
public async Task < Command > Slug (
string ffmpegPath ,
Channel channel ,
DateTimeOffset now ,
TimeSpan duration ,
bool hlsRealtime ,
TimeSpan ptsOffset )
{
FFmpegPlaybackSettings playbackSettings = FFmpegPlaybackSettingsCalculator . CalculateGeneratedImageSettings (
channel . StreamingMode ,
channel . FFmpegProfile ,
hlsRealtime ) ;
Resolution desiredResolution = channel . FFmpegProfile . Resolution ;
string audioFormat = playbackSettings . AudioFormat switch
{
FFmpegProfileAudioFormat . Ac3 = > AudioFormat . Ac3 ,
FFmpegProfileAudioFormat . AacLatm = > AudioFormat . AacLatm ,
_ = > AudioFormat . Aac
} ;
var audioState = new AudioState (
audioFormat ,
playbackSettings . AudioChannels ,
playbackSettings . AudioBitrate ,
playbackSettings . AudioBufferSize ,
playbackSettings . AudioSampleRate ,
false ,
AudioFilter . None ,
playbackSettings . TargetLoudness ) ;
string videoFormat = GetVideoFormat ( playbackSettings ) ;
var desiredState = new FrameState (
playbackSettings . RealtimeOutput ,
InfiniteLoop : false ,
videoFormat ,
GetVideoProfile ( videoFormat , channel . FFmpegProfile . VideoProfile ) ,
VideoPreset . Unset ,
channel . FFmpegProfile . AllowBFrames ,
new PixelFormatYuv420P ( ) ,
new FrameSize ( desiredResolution . Width , desiredResolution . Height ) ,
new FrameSize ( desiredResolution . Width , desiredResolution . Height ) ,
Option < FrameSize > . None ,
channel . FFmpegProfile . PadMode is FilterMode . HardwareIfPossible
? FFmpegFilterMode . HardwareIfPossible
: FFmpegFilterMode . Software ,
IsAnamorphic : false ,
Option < FrameRate > . None ,
playbackSettings . VideoBitrate ,
playbackSettings . VideoBufferSize ,
playbackSettings . VideoTrackTimeScale ,
playbackSettings . NormalizeColors ,
playbackSettings . Deinterlace ) ;
var frameRate = playbackSettings . FrameRate . IfNone ( new FrameRate ( "24" ) ) ;
var ffmpegVideoStream = new VideoStream (
0 ,
VideoFormat . GeneratedImage ,
string . Empty ,
new PixelFormatUnknown ( ) , // leave this unknown so we convert to desired yuv420p
ColorParams . Default ,
desiredState . PaddedSize ,
MaybeSampleAspectRatio : "1:1" ,
DisplayAspectRatio : string . Empty ,
frameRate ,
true ,
ScanKind . Progressive ) ;
var videoInputFile = new LavfiInputFile (
$"color=c=black:s={desiredState.PaddedSize.Width}x{desiredState.PaddedSize.Height}:r={frameRate.FrameRateString}:d={duration.TotalSeconds}" ,
ffmpegVideoStream ) ;
HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode ( playbackSettings ) ;
var hlsOptions = GetHlsOptions ( channel , now , ptsOffset , audioFormat ) ;
var ffmpegState = new FFmpegState (
channel . Number = = FileSystemLayout . TranscodeTroubleshootingChannel ,
HardwareAccelerationMode . None , // no hw accel decode since errors loop
hwAccel ,
VaapiDriverName ( hwAccel , channel . FFmpegProfile . VaapiDriver ) ,
VaapiDeviceName ( hwAccel , channel . FFmpegProfile . VaapiDevice ) ,
playbackSettings . StreamSeek ,
duration ,
channel . StreamingMode ! = StreamingMode . HttpLiveStreamingDirect ,
"ErsatzTV" ,
channel . Name ,
None ,
None ,
None ,
hlsOptions . OutputFormat ,
hlsOptions . HlsPlaylistPath ,
hlsOptions . HlsSegmentTemplate ,
hlsOptions . HlsInitTemplate ,
hlsOptions . HlsSegmentOptions ,
ptsOffset ,
Option < int > . None ,
Optional ( channel . FFmpegProfile . QsvExtraHardwareFrames ) ,
false ,
false ,
GetTonemapAlgorithm ( playbackSettings ) ,
channel . Number = = FileSystemLayout . TranscodeTroubleshootingChannel ) ;
var audioInputFile = new NullAudioInputFile ( audioState ) ;
IPipelineBuilder pipelineBuilder = await _ pipelineBuilderFactory . GetBuilder (
hwAccel ,
videoInputFile ,
audioInputFile ,
Option < WatermarkInputFile > . None ,
Option < SubtitleInputFile > . None ,
Option < ConcatInputFile > . None ,
Option < GraphicsEngineInput > . None ,
VaapiDisplayName ( hwAccel , channel . FFmpegProfile . VaapiDisplay ) ,
VaapiDriverName ( hwAccel , channel . FFmpegProfile . VaapiDriver ) ,
VaapiDeviceName ( hwAccel , channel . FFmpegProfile . VaapiDevice ) ,
channel . Number = = FileSystemLayout . TranscodeTroubleshootingChannel
? FileSystemLayout . TranscodeTroubleshootingFolder
: FileSystemLayout . FFmpegReportsFolder ,
FileSystemLayout . FontsCacheFolder ,
ffmpegPath ) ;
FFmpegPipeline pipeline = pipelineBuilder . Build ( ffmpegState , desiredState ) ;
return GetCommand ( ffmpegPath , videoInputFile , audioInputFile , None , None , None , pipeline ) ;
}
public async Task < Command > ConcatChannel (
string ffmpegPath ,
bool saveReports ,
@ -1260,4 +1341,67 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
@@ -1260,4 +1341,67 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
return false ;
}
private static HlsOptions GetHlsOptions ( Channel channel , DateTimeOffset now , TimeSpan ptsOffset , string audioFormat )
{
OutputFormatKind outputFormat = OutputFormatKind . MpegTs ;
switch ( channel . StreamingMode )
{
case StreamingMode . HttpLiveStreamingSegmenter :
outputFormat = OutputFormatKind . Hls ;
break ;
}
Option < string > hlsPlaylistPath = outputFormat is OutputFormatKind . Hls or OutputFormatKind . HlsMp4
? Path . Combine ( FileSystemLayout . TranscodeFolder , channel . Number , "live.m3u8" )
: Option < string > . None ;
long nowSeconds = now . ToUnixTimeSeconds ( ) ;
Option < string > hlsSegmentTemplate = outputFormat switch
{
OutputFormatKind . Hls = > Path . Combine ( FileSystemLayout . TranscodeFolder , channel . Number , "live%06d.ts" ) ,
OutputFormatKind . HlsMp4 = > Path . Combine (
FileSystemLayout . TranscodeFolder ,
channel . Number ,
$"live_{nowSeconds}_%06d.m4s" ) ,
_ = > Option < string > . None
} ;
Option < string > hlsInitTemplate = outputFormat switch
{
OutputFormatKind . HlsMp4 = > $"{nowSeconds}_init.mp4" ,
_ = > Option < string > . None
} ;
Option < string > hlsSegmentOptions = Option < string > . None ;
if ( outputFormat is OutputFormatKind . Hls )
{
string options = string . Empty ;
if ( ptsOffset = = TimeSpan . Zero )
{
options + = "+initial_discontinuity" ;
}
if ( audioFormat = = AudioFormat . AacLatm )
{
options + = "+latm" ;
}
if ( ! string . IsNullOrWhiteSpace ( options ) )
{
hlsSegmentOptions = $"mpegts_flags={options}" ;
}
}
return new HlsOptions ( outputFormat , hlsPlaylistPath , hlsSegmentTemplate , hlsInitTemplate , hlsSegmentOptions ) ;
}
private sealed record HlsOptions (
OutputFormatKind OutputFormat ,
Option < string > HlsPlaylistPath ,
Option < string > HlsSegmentTemplate ,
Option < string > HlsInitTemplate ,
Option < string > HlsSegmentOptions ) ;
}