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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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>();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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>();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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>();
|
||||
|
||||
+27
-6
@@ -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>();
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user