Browse Source

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

* add LIBVA_DRIVER_NAME env var

* add vaapi device name

* add FFREPORT env var

* fixes
pull/636/head
Jason Dove 3 years ago committed by GitHub
parent
commit
2f2d7952dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 45
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  2. 14
      ErsatzTV.FFmpeg.Tests/PipelineBuilderTests.cs
  3. 9
      ErsatzTV.FFmpeg/CommandGenerator.cs
  4. 2
      ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderH264Cuvid.cs
  5. 3
      ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderHevcCuvid.cs
  6. 4
      ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs
  7. 2
      ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg4Cuvid.cs
  8. 3
      ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderVc1Cuvid.cs
  9. 3
      ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderVp9Cuvid.cs
  10. 7
      ErsatzTV.FFmpeg/Decoder/DecoderBase.cs
  11. 2
      ErsatzTV.FFmpeg/Decoder/DecoderH264.cs
  12. 2
      ErsatzTV.FFmpeg/Decoder/DecoderHevc.cs
  13. 2
      ErsatzTV.FFmpeg/Decoder/DecoderImplicit.cs
  14. 2
      ErsatzTV.FFmpeg/Decoder/DecoderMpeg1.cs
  15. 2
      ErsatzTV.FFmpeg/Decoder/DecoderMpeg2.cs
  16. 2
      ErsatzTV.FFmpeg/Decoder/DecoderMpeg4.cs
  17. 2
      ErsatzTV.FFmpeg/Decoder/DecoderMsMpeg4v2.cs
  18. 2
      ErsatzTV.FFmpeg/Decoder/DecoderMsMpeg4v3.cs
  19. 2
      ErsatzTV.FFmpeg/Decoder/DecoderVaapi.cs
  20. 2
      ErsatzTV.FFmpeg/Decoder/DecoderVc1.cs
  21. 2
      ErsatzTV.FFmpeg/Decoder/DecoderVp9.cs
  22. 2
      ErsatzTV.FFmpeg/Decoder/Qsv/DecoderH264Qsv.cs
  23. 2
      ErsatzTV.FFmpeg/Decoder/Qsv/DecoderHevcQsv.cs
  24. 2
      ErsatzTV.FFmpeg/Decoder/Qsv/DecoderMpeg2Qsv.cs
  25. 2
      ErsatzTV.FFmpeg/Decoder/Qsv/DecoderVc1Qsv.cs
  26. 2
      ErsatzTV.FFmpeg/Decoder/Qsv/DecoderVp9Qsv.cs
  27. 5
      ErsatzTV.FFmpeg/Encoder/EncoderBase.cs
  28. 3
      ErsatzTV.FFmpeg/Environment/EnvironmentVariable.cs
  29. 46
      ErsatzTV.FFmpeg/Environment/FFReportVariable.cs
  30. 22
      ErsatzTV.FFmpeg/Environment/LibvaDriverNameVariable.cs
  31. 5
      ErsatzTV.FFmpeg/Filter/BaseFilter.cs
  32. 5
      ErsatzTV.FFmpeg/Filter/ComplexFilter.cs
  33. 5
      ErsatzTV.FFmpeg/Format/ConcatInputFormat.cs
  34. 8
      ErsatzTV.FFmpeg/FrameState.cs
  35. 5
      ErsatzTV.FFmpeg/IPipelineStep.cs
  36. 6
      ErsatzTV.FFmpeg/Option/FrameRateOutputOption.cs
  37. 6
      ErsatzTV.FFmpeg/Option/GlobalOption.cs
  38. 33
      ErsatzTV.FFmpeg/Option/HardwareAcceleration/AvailableHardwareAccelerationOptions.cs
  39. 9
      ErsatzTV.FFmpeg/Option/HardwareAcceleration/VaapiHardwareAccelerationOption.cs
  40. 5
      ErsatzTV.FFmpeg/Option/InfiniteLoopInputOption.cs
  41. 5
      ErsatzTV.FFmpeg/Option/OutputOption.cs
  42. 6
      ErsatzTV.FFmpeg/Option/RealtimeInputOption.cs
  43. 5
      ErsatzTV.FFmpeg/Option/StreamSeekInputOption.cs
  44. 5
      ErsatzTV.FFmpeg/Option/TimeLimitOutputOption.cs
  45. 5
      ErsatzTV.FFmpeg/OutputFormat/OutputFormatHls.cs
  46. 6
      ErsatzTV.FFmpeg/OutputFormat/OutputFormatMpegTs.cs
  47. 49
      ErsatzTV.FFmpeg/PipelineBuilder.cs
  48. 6
      ErsatzTV.FFmpeg/Protocol/PipeProtocol.cs
  49. 3
      ErsatzTV.sln.DotSettings

45
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -2,12 +2,14 @@ @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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;
}
}

14
ErsatzTV.FFmpeg.Tests/PipelineBuilderTests.cs

@ -30,7 +30,10 @@ public class PipelineGeneratorTests @@ -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 @@ -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 @@ -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 @@ -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 @@ -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);

9
ErsatzTV.FFmpeg/CommandGenerator.cs

@ -1,7 +1,14 @@ @@ -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)

2
ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderH264Cuvid.cs

@ -30,7 +30,7 @@ public class DecoderH264Cuvid : DecoderBase @@ -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)
{

3
ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderHevcCuvid.cs

@ -28,7 +28,8 @@ public class DecoderHevcCuvid : DecoderBase @@ -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)
{

4
ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs

@ -35,8 +35,8 @@ public class DecoderMpeg2Cuvid : DecoderBase @@ -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)
{

2
ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg4Cuvid.cs

@ -30,7 +30,7 @@ public class DecoderMpeg4Cuvid : DecoderBase @@ -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)
{

3
ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderVc1Cuvid.cs

@ -28,7 +28,8 @@ public class DecoderVc1Cuvid : DecoderBase @@ -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)
{

3
ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderVp9Cuvid.cs

@ -28,7 +28,8 @@ public class DecoderVp9Cuvid : DecoderBase @@ -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)
{

7
ErsatzTV.FFmpeg/Decoder/DecoderBase.cs

@ -1,8 +1,11 @@ @@ -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>();

2
ErsatzTV.FFmpeg/Decoder/DecoderH264.cs

@ -3,5 +3,5 @@ @@ -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;
}

2
ErsatzTV.FFmpeg/Decoder/DecoderHevc.cs

@ -3,5 +3,5 @@ @@ -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;
}

2
ErsatzTV.FFmpeg/Decoder/DecoderImplicit.cs

@ -2,7 +2,7 @@ @@ -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>();
}

2
ErsatzTV.FFmpeg/Decoder/DecoderMpeg1.cs

@ -3,5 +3,5 @@ @@ -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;
}

2
ErsatzTV.FFmpeg/Decoder/DecoderMpeg2.cs

@ -3,5 +3,5 @@ @@ -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;
}

2
ErsatzTV.FFmpeg/Decoder/DecoderMpeg4.cs

@ -3,5 +3,5 @@ @@ -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;
}

2
ErsatzTV.FFmpeg/Decoder/DecoderMsMpeg4v2.cs

@ -3,5 +3,5 @@ @@ -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;
}

2
ErsatzTV.FFmpeg/Decoder/DecoderMsMpeg4v3.cs

@ -3,5 +3,5 @@ @@ -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;
}

2
ErsatzTV.FFmpeg/Decoder/DecoderVaapi.cs

@ -4,7 +4,7 @@ namespace ErsatzTV.FFmpeg.Decoder; @@ -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>();

2
ErsatzTV.FFmpeg/Decoder/DecoderVc1.cs

@ -3,5 +3,5 @@ @@ -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;
}

2
ErsatzTV.FFmpeg/Decoder/DecoderVp9.cs

@ -3,5 +3,5 @@ @@ -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;
}

2
ErsatzTV.FFmpeg/Decoder/Qsv/DecoderH264Qsv.cs

@ -6,7 +6,7 @@ public class DecoderH264Qsv : DecoderBase @@ -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)
{

2
ErsatzTV.FFmpeg/Decoder/Qsv/DecoderHevcQsv.cs

@ -4,5 +4,5 @@ public class DecoderHevcQsv : DecoderBase @@ -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;
}

2
ErsatzTV.FFmpeg/Decoder/Qsv/DecoderMpeg2Qsv.cs

@ -4,5 +4,5 @@ public class DecoderMpeg2Qsv : DecoderBase @@ -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;
}

2
ErsatzTV.FFmpeg/Decoder/Qsv/DecoderVc1Qsv.cs

@ -4,5 +4,5 @@ public class DecoderVc1Qsv : DecoderBase @@ -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;
}

2
ErsatzTV.FFmpeg/Decoder/Qsv/DecoderVp9Qsv.cs

@ -4,5 +4,5 @@ public class DecoderVp9Qsv : DecoderBase @@ -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;
}

5
ErsatzTV.FFmpeg/Encoder/EncoderBase.cs

@ -1,7 +1,10 @@ @@ -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>();

3
ErsatzTV.FFmpeg/Environment/EnvironmentVariable.cs

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
namespace ErsatzTV.FFmpeg.Environment;
public record EnvironmentVariable(string Key, string Value);

46
ErsatzTV.FFmpeg/Environment/FFReportVariable.cs

@ -0,0 +1,46 @@ @@ -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 };
}

22
ErsatzTV.FFmpeg/Environment/LibvaDriverNameVariable.cs

@ -0,0 +1,22 @@ @@ -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 };
}

5
ErsatzTV.FFmpeg/Filter/BaseFilter.cs

@ -1,7 +1,10 @@ @@ -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>();

5
ErsatzTV.FFmpeg/Filter/ComplexFilter.cs

@ -1,4 +1,6 @@ @@ -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 @@ -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();

5
ErsatzTV.FFmpeg/Format/ConcatInputFormat.cs

@ -1,7 +1,10 @@ @@ -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>

8
ErsatzTV.FFmpeg/FrameState.cs

@ -5,7 +5,10 @@ using LanguageExt; @@ -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( @@ -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,

5
ErsatzTV.FFmpeg/IPipelineStep.cs

@ -1,7 +1,10 @@ @@ -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; }

6
ErsatzTV.FFmpeg/Option/FrameRateOutputOption.cs

@ -1,4 +1,6 @@ @@ -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 @@ -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>();

6
ErsatzTV.FFmpeg/Option/GlobalOption.cs

@ -1,8 +1,10 @@ @@ -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>();

33
ErsatzTV.FFmpeg/Option/HardwareAcceleration/AvailableHardwareAccelerationOptions.cs

@ -1,16 +1,37 @@ @@ -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;
}
}

9
ErsatzTV.FFmpeg/Option/HardwareAcceleration/VaapiHardwareAccelerationOption.cs

@ -2,8 +2,15 @@ @@ -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
{

5
ErsatzTV.FFmpeg/Option/InfiniteLoopInputOption.cs

@ -1,4 +1,6 @@ @@ -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 @@ -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>();

5
ErsatzTV.FFmpeg/Option/OutputOption.cs

@ -1,8 +1,11 @@ @@ -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>();

6
ErsatzTV.FFmpeg/Option/RealtimeInputOption.cs

@ -1,7 +1,11 @@ @@ -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" };

5
ErsatzTV.FFmpeg/Option/StreamSeekInputOption.cs

@ -1,4 +1,6 @@ @@ -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 @@ -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}" };

5
ErsatzTV.FFmpeg/Option/TimeLimitOutputOption.cs

@ -1,4 +1,6 @@ @@ -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 @@ -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>();

5
ErsatzTV.FFmpeg/OutputFormat/OutputFormatHls.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using LanguageExt;
using ErsatzTV.FFmpeg.Environment;
using LanguageExt;
namespace ErsatzTV.FFmpeg.OutputFormat;
@ -21,7 +22,7 @@ public class OutputFormatHls : IPipelineStep @@ -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>();

6
ErsatzTV.FFmpeg/OutputFormat/OutputFormatMpegTs.cs

@ -1,8 +1,10 @@ @@ -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>();

49
ErsatzTV.FFmpeg/PipelineBuilder.cs

@ -1,5 +1,6 @@ @@ -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 @@ -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 @@ -38,6 +40,7 @@ public class PipelineBuilder
_videoFilterSteps = new List<IPipelineFilterStep>();
_inputFiles = inputFiles;
_reportsFolder = reportsFolder;
_logger = logger;
}
@ -68,7 +71,10 @@ public class PipelineBuilder @@ -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 @@ -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 @@ -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))

6
ErsatzTV.FFmpeg/Protocol/PipeProtocol.cs

@ -1,8 +1,10 @@ @@ -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
ErsatzTV.sln.DotSettings

@ -1,6 +1,7 @@ @@ -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 @@ @@ -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 @@ @@ -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>

Loading…
Cancel
Save