use vaapi driver settings with new transcoder logic (#635)

* add LIBVA_DRIVER_NAME env var

* add vaapi device name

* add FFREPORT env var

* fixes
This commit is contained in:
Jason Dove
2022-02-15 08:44:49 -06:00
committed by GitHub
parent c96b800b52
commit 2f2d7952dd
49 changed files with 310 additions and 67 deletions
@@ -2,12 +2,14 @@
using System.Diagnostics;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.FFmpeg;
using ErsatzTV.FFmpeg.Environment;
using ErsatzTV.FFmpeg.Format;
using ErsatzTV.FFmpeg.OutputFormat;
using LanguageExt;
@@ -122,7 +124,10 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
: Option<string>.None;
var desiredState = new FrameState(
saveReports,
hwAccel,
VaapiDriverName(hwAccel, vaapiDriver),
VaapiDeviceName(hwAccel, vaapiDevice),
playbackSettings.RealtimeOutput,
false,
playbackSettings.StreamSeek,
@@ -170,7 +175,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
public Process ConcatChannel(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host)
{
var resolution = new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height);
var desiredState = FrameState.Concat(channel.Name, resolution);
var desiredState = FrameState.Concat(saveReports, channel.Name, resolution);
var inputFiles = new List<InputFile>
{
@@ -218,12 +223,13 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
private Process GetProcess(string ffmpegPath, IList<InputFile> inputFiles, FrameState desiredState)
{
var pipelineBuilder = new PipelineBuilder(inputFiles, _logger);
var pipelineBuilder = new PipelineBuilder(inputFiles, FileSystemLayout.FFmpegReportsFolder, _logger);
IList<IPipelineStep> pipelineSteps = pipelineBuilder.Build(desiredState);
_logger.LogDebug("FFmpeg pipeline {PipelineSteps}", pipelineSteps);
_logger.LogDebug("FFmpeg pipeline {PipelineSteps}", pipelineSteps.Map(ps => ps.GetType().Name));
IList<EnvironmentVariable> environmentVariables = CommandGenerator.GenerateEnvironmentVariables(pipelineSteps);
IList<string> arguments = CommandGenerator.GenerateArguments(inputFiles, pipelineSteps);
var startInfo = new ProcessStartInfo
@@ -236,6 +242,16 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
StandardOutputEncoding = Encoding.UTF8
};
if (environmentVariables.Any())
{
_logger.LogDebug("FFmpeg environment variables {EnvVars}", environmentVariables);
}
foreach ((string key, string value) in environmentVariables)
{
startInfo.EnvironmentVariables[key] = value;
}
foreach (string argument in arguments)
{
startInfo.ArgumentList.Add(argument);
@@ -246,4 +262,27 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
StartInfo = startInfo
};
}
private static Option<string> VaapiDriverName(HardwareAccelerationMode accelerationMode, VaapiDriver driver)
{
if (accelerationMode == HardwareAccelerationMode.Vaapi)
{
switch (driver)
{
case VaapiDriver.i965:
return "i965";
case VaapiDriver.iHD:
return "iHD";
case VaapiDriver.RadeonSI:
return "radeonsi";
}
}
return Option<string>.None;
}
private static Option<string> VaapiDeviceName(HardwareAccelerationMode accelerationMode, string vaapiDevice)
{
return accelerationMode == HardwareAccelerationMode.Vaapi ? vaapiDevice : Option<string>.None;
}
}
+10 -4
View File
@@ -30,7 +30,10 @@ public class PipelineGeneratorTests
var inputFiles = new List<InputFile> { testFile };
var desiredState = new FrameState(
false,
HardwareAccelerationMode.None,
Option<string>.None,
Option<string>.None,
true,
false,
Option<TimeSpan>.None,
@@ -60,7 +63,7 @@ public class PipelineGeneratorTests
Option<string>.None,
0);
var builder = new PipelineBuilder(inputFiles, _logger);
var builder = new PipelineBuilder(inputFiles, "", _logger);
IList<IPipelineStep> result = builder.Build(desiredState);
result.Should().HaveCountGreaterThan(0);
@@ -83,7 +86,10 @@ public class PipelineGeneratorTests
var inputFiles = new List<InputFile> { testFile };
var desiredState = new FrameState(
false,
HardwareAccelerationMode.None,
Option<string>.None,
Option<string>.None,
true,
false,
Option<TimeSpan>.None,
@@ -113,7 +119,7 @@ public class PipelineGeneratorTests
Option<string>.None,
0);
var builder = new PipelineBuilder(inputFiles, _logger);
var builder = new PipelineBuilder(inputFiles, "", _logger);
IList<IPipelineStep> result = builder.Build(desiredState);
result.Should().HaveCountGreaterThan(0);
@@ -125,14 +131,14 @@ public class PipelineGeneratorTests
public void Concat_Test()
{
var resolution = new FrameSize(1920, 1080);
var desiredState = FrameState.Concat("Some Channel", resolution);
var desiredState = FrameState.Concat(false, "Some Channel", resolution);
var inputFiles = new List<InputFile>
{
new ConcatInputFile("http://localhost:8080/ffmpeg/concat/1", resolution)
};
var builder = new PipelineBuilder(inputFiles, _logger);
var builder = new PipelineBuilder(inputFiles, "", _logger);
IList<IPipelineStep> result = builder.Build(desiredState);
result.Should().HaveCountGreaterThan(0);
+8 -1
View File
@@ -1,7 +1,14 @@
namespace ErsatzTV.FFmpeg;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg;
public static class CommandGenerator
{
public static IList<EnvironmentVariable> GenerateEnvironmentVariables(IEnumerable<IPipelineStep> pipelineSteps)
{
return pipelineSteps.SelectMany(ps => ps.EnvironmentVariables).ToList();
}
public static IList<string> GenerateArguments(
IEnumerable<InputFile> inputFiles,
IList<IPipelineStep> pipelineSteps)
@@ -30,7 +30,7 @@ public class DecoderH264Cuvid : DecoderBase
}
}
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
public override FrameState NextState(FrameState currentState)
{
@@ -28,7 +28,8 @@ public class DecoderHevcCuvid : DecoderBase
return result;
}
}
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
public override FrameState NextState(FrameState currentState)
{
@@ -35,8 +35,8 @@ public class DecoderMpeg2Cuvid : DecoderBase
return result;
}
}
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
public override FrameState NextState(FrameState currentState)
{
@@ -30,7 +30,7 @@ public class DecoderMpeg4Cuvid : DecoderBase
}
}
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
public override FrameState NextState(FrameState currentState)
{
@@ -28,7 +28,8 @@ public class DecoderVc1Cuvid : DecoderBase
return result;
}
}
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
public override FrameState NextState(FrameState currentState)
{
@@ -28,7 +28,8 @@ public class DecoderVp9Cuvid : DecoderBase
return result;
}
}
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
public override FrameState NextState(FrameState currentState)
{
+5 -2
View File
@@ -1,8 +1,11 @@
namespace ErsatzTV.FFmpeg.Decoder;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Decoder;
public abstract class DecoderBase : IDecoder
{
public abstract FrameDataLocation OutputFrameDataLocation { get; }
protected abstract FrameDataLocation OutputFrameDataLocation { get; }
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public virtual IList<string> InputOptions => new List<string> { "-c:v", Name };
public IList<string> FilterOptions => Array.Empty<string>();
+1 -1
View File
@@ -3,5 +3,5 @@
public class DecoderH264 : DecoderBase
{
public override string Name => "h264";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
}
+1 -1
View File
@@ -3,5 +3,5 @@
public class DecoderHevc : DecoderBase
{
public override string Name => "hevc";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
}
+1 -1
View File
@@ -2,7 +2,7 @@
public class DecoderImplicit : DecoderBase
{
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
public override string Name => string.Empty;
public override IList<string> InputOptions => Array.Empty<string>();
}
+1 -1
View File
@@ -3,5 +3,5 @@
public class DecoderMpeg1Video : DecoderBase
{
public override string Name => "mpeg1video";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
}
+1 -1
View File
@@ -3,5 +3,5 @@
public class DecoderMpeg2Video : DecoderBase
{
public override string Name => "mpeg2video";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
}
+1 -1
View File
@@ -3,5 +3,5 @@
public class DecoderMpeg4 : DecoderBase
{
public override string Name => "mpeg4";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
}
+1 -1
View File
@@ -3,5 +3,5 @@
public class DecoderMsMpeg4V2 : DecoderBase
{
public override string Name => "msmpeg4v2";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
}
+1 -1
View File
@@ -3,5 +3,5 @@
public class DecoderMsMpeg4V3 : DecoderBase
{
public override string Name => "msmpeg4v3";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
}
+1 -1
View File
@@ -4,7 +4,7 @@ namespace ErsatzTV.FFmpeg.Decoder;
public class DecoderVaapi : DecoderBase
{
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
public override string Name => "implicit_vaapi";
public override IList<string> InputOptions => Array.Empty<string>();
+1 -1
View File
@@ -3,5 +3,5 @@
public class DecoderVc1 : DecoderBase
{
public override string Name => "vc1";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
}
+1 -1
View File
@@ -3,5 +3,5 @@
public class DecoderVp9 : DecoderBase
{
public override string Name => "vp9";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
}
@@ -6,7 +6,7 @@ public class DecoderH264Qsv : DecoderBase
{
public override string Name => "h264_qsv";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
public override FrameState NextState(FrameState currentState)
{
@@ -4,5 +4,5 @@ public class DecoderHevcQsv : DecoderBase
{
public override string Name => "hevc_qsv";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
}
@@ -4,5 +4,5 @@ public class DecoderMpeg2Qsv : DecoderBase
{
public override string Name => "mpeg2_qsv";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
}
+1 -1
View File
@@ -4,5 +4,5 @@ public class DecoderVc1Qsv : DecoderBase
{
public override string Name => "vc1_qsv";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
}
+1 -1
View File
@@ -4,5 +4,5 @@ public class DecoderVp9Qsv : DecoderBase
{
public override string Name => "vp9_qsv";
public override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
}
+4 -1
View File
@@ -1,7 +1,10 @@
namespace ErsatzTV.FFmpeg.Encoder;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Encoder;
public abstract class EncoderBase : IEncoder
{
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions => Array.Empty<string>();
public IList<string> FilterOptions => Array.Empty<string>();
@@ -0,0 +1,3 @@
namespace ErsatzTV.FFmpeg.Environment;
public record EnvironmentVariable(string Key, string Value);
@@ -0,0 +1,46 @@
using System.Runtime.InteropServices;
namespace ErsatzTV.FFmpeg.Environment;
public class FFReportVariable : IPipelineStep
{
private readonly string _reportsFolder;
private readonly IList<InputFile> _inputFiles;
public FFReportVariable(string reportsFolder, IList<InputFile> inputFiles)
{
_reportsFolder = reportsFolder;
_inputFiles = inputFiles;
}
public IList<EnvironmentVariable> EnvironmentVariables
{
get
{
string fileName = _inputFiles.OfType<ConcatInputFile>().Any()
? Path.Combine(_reportsFolder, "ffmpeg-%t-concat.log")
: Path.Combine(_reportsFolder, "ffmpeg-%t-transcode.log");
// rework filename in a format that works on windows
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// \ is escape, so use / for directory separators
fileName = fileName.Replace(@"\", @"/");
// colon after drive letter needs to be escaped
fileName = fileName.Replace(@":/", @"\:/");
}
return new List<EnvironmentVariable>
{
new("FFREPORT", $"file={fileName}:level=32")
};
}
}
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions => Array.Empty<string>();
public IList<string> FilterOptions => Array.Empty<string>();
public IList<string> OutputOptions => Array.Empty<string>();
public FrameState NextState(FrameState currentState) => currentState with { SaveReport = true };
}
@@ -0,0 +1,22 @@
namespace ErsatzTV.FFmpeg.Environment;
public class LibvaDriverNameVariable : IPipelineStep
{
private readonly string _driverName;
public LibvaDriverNameVariable(string driverName)
{
_driverName = driverName;
}
public IList<EnvironmentVariable> EnvironmentVariables => new List<EnvironmentVariable>
{
new("LIBVA_DRIVER_NAME", _driverName)
};
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions => Array.Empty<string>();
public IList<string> FilterOptions => Array.Empty<string>();
public IList<string> OutputOptions => Array.Empty<string>();
public FrameState NextState(FrameState currentState) => currentState with { VaapiDriver = _driverName };
}
+4 -1
View File
@@ -1,7 +1,10 @@
namespace ErsatzTV.FFmpeg.Filter;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Filter;
public abstract class BaseFilter : IPipelineFilterStep
{
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public virtual IList<string> GlobalOptions => Array.Empty<string>();
public virtual IList<string> InputOptions => Array.Empty<string>();
public virtual IList<string> FilterOptions => Array.Empty<string>();
+4 -1
View File
@@ -1,4 +1,6 @@
namespace ErsatzTV.FFmpeg.Filter;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Filter;
public class ComplexFilter : IPipelineStep
{
@@ -73,6 +75,7 @@ public class ComplexFilter : IPipelineStep
return result;
}
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions => Array.Empty<string>();
public IList<string> FilterOptions => Arguments();
+4 -1
View File
@@ -1,7 +1,10 @@
namespace ErsatzTV.FFmpeg.Format;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Format;
public class ConcatInputFormat : IPipelineStep
{
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions => new List<string>
+7 -1
View File
@@ -5,7 +5,10 @@ using LanguageExt;
namespace ErsatzTV.FFmpeg;
public record FrameState(
bool SaveReport,
HardwareAccelerationMode HardwareAccelerationMode,
Option<string> VaapiDriver,
Option<string> VaapiDevice,
bool Realtime,
bool InfiniteLoop,
Option<TimeSpan> Start,
@@ -36,9 +39,12 @@ public record FrameState(
long PtsOffset,
FrameDataLocation FrameDataLocation = FrameDataLocation.Unknown)
{
public static FrameState Concat(string channelName, FrameSize resolution) =>
public static FrameState Concat(bool saveReport, string channelName, FrameSize resolution) =>
new(
saveReport,
HardwareAccelerationMode.None,
Option<string>.None,
Option<string>.None,
true, // realtime
true, // infinite loop
Option<TimeSpan>.None,
+4 -1
View File
@@ -1,7 +1,10 @@
namespace ErsatzTV.FFmpeg;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg;
public interface IPipelineStep
{
IList<EnvironmentVariable> EnvironmentVariables { get; }
IList<string> GlobalOptions { get; }
IList<string> InputOptions { get; }
IList<string> FilterOptions { get; }
@@ -1,4 +1,6 @@
namespace ErsatzTV.FFmpeg.Option;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Option;
public class FrameRateOutputOption : IPipelineStep
{
@@ -9,7 +11,7 @@ public class FrameRateOutputOption : IPipelineStep
_frameRate = frameRate;
}
public FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Unknown;
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions => Array.Empty<string>();
public IList<string> FilterOptions => Array.Empty<string>();
+4 -2
View File
@@ -1,8 +1,10 @@
namespace ErsatzTV.FFmpeg.Option;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Option;
public abstract class GlobalOption : IPipelineStep
{
public FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Unknown;
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public abstract IList<string> GlobalOptions { get; }
public IList<string> InputOptions => Array.Empty<string>();
public IList<string> FilterOptions => Array.Empty<string>();
@@ -1,16 +1,37 @@
namespace ErsatzTV.FFmpeg.Option.HardwareAcceleration;
using LanguageExt;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.FFmpeg.Option.HardwareAcceleration;
public static class AvailableHardwareAccelerationOptions
{
public static IPipelineStep ForMode(HardwareAccelerationMode mode)
{
return mode switch
public static Option<IPipelineStep> ForMode(
HardwareAccelerationMode mode,
Option<string> vaapiDevice,
ILogger logger) =>
mode switch
{
HardwareAccelerationMode.Nvenc => new CudaHardwareAccelerationOption(),
HardwareAccelerationMode.Qsv => new QsvHardwareAccelerationOption(),
HardwareAccelerationMode.Vaapi => new VaapiHardwareAccelerationOption(),
HardwareAccelerationMode.Vaapi => GetVaapiAcceleration(vaapiDevice, logger),
HardwareAccelerationMode.VideoToolbox => new VideoToolboxHardwareAccelerationOption(),
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null)
_ => LogUnknownMode(mode, logger)
};
private static Option<IPipelineStep> GetVaapiAcceleration(Option<string> vaapiDevice, ILogger logger)
{
foreach (string device in vaapiDevice)
{
return new VaapiHardwareAccelerationOption(device);
}
logger.LogWarning("VAAPI device name is missing; falling back to software mode");
return Option<IPipelineStep>.None;
}
private static Option<IPipelineStep> LogUnknownMode(HardwareAccelerationMode mode, ILogger logger)
{
logger.LogWarning("Unexpected hardware acceleration mode {AccelMode}; may have playback issues", mode);
return Option<IPipelineStep>.None;
}
}
@@ -2,8 +2,15 @@
public class VaapiHardwareAccelerationOption : GlobalOption
{
private readonly string _vaapiDevice;
public VaapiHardwareAccelerationOption(string vaapiDevice)
{
_vaapiDevice = vaapiDevice;
}
public override IList<string> GlobalOptions => new List<string>
{ "-hwaccel", "vaapi", "-hwaccel_output_format", "vaapi" };
{ "-hwaccel", "vaapi", "-vaapi_device", _vaapiDevice, "-hwaccel_output_format", "vaapi" };
public override FrameState NextState(FrameState currentState) => currentState with
{
@@ -1,4 +1,6 @@
namespace ErsatzTV.FFmpeg.Option;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Option;
public class InfiniteLoopInputOption : IPipelineStep
{
@@ -9,6 +11,7 @@ public class InfiniteLoopInputOption : IPipelineStep
_currentState = currentState;
}
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions => new List<string> { "-stream_loop", "-1" };
public IList<string> FilterOptions => Array.Empty<string>();
+4 -1
View File
@@ -1,8 +1,11 @@
namespace ErsatzTV.FFmpeg.Option;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Option;
public abstract class OutputOption : IPipelineStep
{
public FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Unknown;
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions => Array.Empty<string>();
public IList<string> FilterOptions => Array.Empty<string>();
@@ -1,7 +1,11 @@
namespace ErsatzTV.FFmpeg.Option;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Option;
public class RealtimeInputOption : IPipelineStep
{
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
// some builds of ffmpeg seem to hang when realtime input is requested with multithreading,
// so we force a single thread here
public IList<string> GlobalOptions => new List<string> { "-threads", "1" };
@@ -1,4 +1,6 @@
namespace ErsatzTV.FFmpeg.Option;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Option;
public class StreamSeekInputOption : IPipelineStep
{
@@ -10,6 +12,7 @@ public class StreamSeekInputOption : IPipelineStep
}
public FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Unknown;
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions =>
_start == TimeSpan.Zero ? Array.Empty<string>() : new List<string> { "-ss", $"{_start:c}" };
@@ -1,4 +1,6 @@
namespace ErsatzTV.FFmpeg.Option;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Option;
public class TimeLimitOutputOption : IPipelineStep
{
@@ -10,6 +12,7 @@ public class TimeLimitOutputOption : IPipelineStep
}
public FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Unknown;
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions => Array.Empty<string>();
public IList<string> FilterOptions => Array.Empty<string>();
@@ -1,4 +1,5 @@
using LanguageExt;
using ErsatzTV.FFmpeg.Environment;
using LanguageExt;
namespace ErsatzTV.FFmpeg.OutputFormat;
@@ -21,7 +22,7 @@ public class OutputFormatHls : IPipelineStep
_playlistPath = playlistPath;
}
public FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions => Array.Empty<string>();
public IList<string> FilterOptions => Array.Empty<string>();
@@ -1,8 +1,10 @@
namespace ErsatzTV.FFmpeg.OutputFormat;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.OutputFormat;
public class OutputFormatMpegTs : IPipelineStep
{
public FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions => Array.Empty<string>();
public IList<string> FilterOptions => Array.Empty<string>();
+43 -6
View File
@@ -1,5 +1,6 @@
using ErsatzTV.FFmpeg.Decoder;
using ErsatzTV.FFmpeg.Encoder;
using ErsatzTV.FFmpeg.Environment;
using ErsatzTV.FFmpeg.Filter;
using ErsatzTV.FFmpeg.Format;
using ErsatzTV.FFmpeg.Option;
@@ -18,9 +19,10 @@ public class PipelineBuilder
private readonly List<IPipelineFilterStep> _audioFilterSteps;
private readonly List<IPipelineFilterStep> _videoFilterSteps;
private readonly IList<InputFile> _inputFiles;
private readonly string _reportsFolder;
private readonly ILogger _logger;
public PipelineBuilder(IList<InputFile> inputFiles, ILogger logger)
public PipelineBuilder(IList<InputFile> inputFiles, string reportsFolder, ILogger logger)
{
_pipelineSteps = new List<IPipelineStep>
{
@@ -38,6 +40,7 @@ public class PipelineBuilder
_videoFilterSteps = new List<IPipelineFilterStep>();
_inputFiles = inputFiles;
_reportsFolder = reportsFolder;
_logger = logger;
}
@@ -68,7 +71,10 @@ public class PipelineBuilder
}
var currentState = new FrameState(
false, // save report
HardwareAccelerationMode.None,
Option<string>.None,
Option<string>.None,
false, // realtime
false, // infinite loop
Option<TimeSpan>.None,
@@ -98,6 +104,13 @@ public class PipelineBuilder
Option<string>.None,
0);
if (desiredState.SaveReport && !currentState.SaveReport)
{
IPipelineStep step = new FFReportVariable(_reportsFolder, _inputFiles);
currentState = step.NextState(currentState);
_pipelineSteps.Add(step);
}
foreach (TimeSpan desiredStart in desiredState.Start)
{
if (currentState.Start != desiredStart)
@@ -129,13 +142,37 @@ public class PipelineBuilder
}
else
{
// TODO: prioritize which codecs are used (hw accel)
if (currentState.HardwareAccelerationMode != desiredState.HardwareAccelerationMode)
{
IPipelineStep accel =
AvailableHardwareAccelerationOptions.ForMode(desiredState.HardwareAccelerationMode);
currentState = accel.NextState(currentState);
_pipelineSteps.Add(accel);
Option<IPipelineStep> maybeAccel = AvailableHardwareAccelerationOptions.ForMode(
desiredState.HardwareAccelerationMode,
desiredState.VaapiDevice,
_logger);
if (maybeAccel.IsNone)
{
desiredState = desiredState with
{
// disable hw accel if we don't match anything
HardwareAccelerationMode = HardwareAccelerationMode.None
};
}
foreach (IPipelineStep accel in maybeAccel)
{
currentState = accel.NextState(currentState);
_pipelineSteps.Add(accel);
}
}
foreach (string desiredVaapiDriver in desiredState.VaapiDriver)
{
if (currentState.VaapiDriver != desiredVaapiDriver)
{
IPipelineStep step = new LibvaDriverNameVariable(desiredVaapiDriver);
currentState = step.NextState(currentState);
_pipelineSteps.Add(step);
}
}
foreach (IDecoder decoder in AvailableDecoders.ForVideoFormat(currentState, desiredState, _logger))
+4 -2
View File
@@ -1,8 +1,10 @@
namespace ErsatzTV.FFmpeg.Protocol;
using ErsatzTV.FFmpeg.Environment;
namespace ErsatzTV.FFmpeg.Protocol;
public class PipeProtocol : IPipelineStep
{
public FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions => Array.Empty<string>();
public IList<string> FilterOptions => Array.Empty<string>();
+3
View File
@@ -1,6 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=ErsatzTV_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DTO/@EntryIndexedValue">DTO</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FF/@EntryIndexedValue">FF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HDHR/@EntryIndexedValue">HDHR</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LE/@EntryIndexedValue">LE</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NV/@EntryIndexedValue">NV</s:String>
@@ -28,6 +29,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=ffconcat/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=fflags/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ffprobe/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=FFREPORT/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Fmpeg/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=fontfile/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Fprobe/@EntryIndexedValue">True</s:Boolean>
@@ -36,6 +38,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=igndts/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Jellyfin/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Libavfilter/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Libva/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=libx/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=maxrate/@EntryIndexedValue">True</s:Boolean>