Browse Source

add qsv transcoding support

pull/41/head
Jason Dove 5 years ago
parent
commit
1a9e035da4
  1. 1
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs
  2. 1
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs
  3. 1
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs
  4. 1
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
  5. 1
      ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs
  6. 1
      ErsatzTV.Application/FFmpegProfiles/Mapper.cs
  7. 28
      ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsServiceTests.cs
  8. 1
      ErsatzTV.Core/Domain/FFmpegProfile.cs
  9. 2
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettings.cs
  10. 5
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
  11. 39
      ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs
  12. 5
      ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs
  13. 1494
      ErsatzTV.Infrastructure/Migrations/20210302122352_Add_FFmpegProfileQsvAcceleration.Designer.cs
  14. 20
      ErsatzTV.Infrastructure/Migrations/20210302122352_Add_FFmpegProfileQsvAcceleration.cs
  15. 3
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  16. 3
      ErsatzTV/Pages/FFmpegEditor.razor
  17. 8
      ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs
  18. 4
      ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

1
ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs

@ -8,6 +8,7 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands
string Name, string Name,
int ThreadCount, int ThreadCount,
bool Transcode, bool Transcode,
bool QsvAcceleration,
int ResolutionId, int ResolutionId,
bool NormalizeResolution, bool NormalizeResolution,
string VideoCodec, string VideoCodec,

1
ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs

@ -41,6 +41,7 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands
Name = name, Name = name,
ThreadCount = threadCount, ThreadCount = threadCount,
Transcode = request.Transcode, Transcode = request.Transcode,
QsvAcceleration = request.QsvAcceleration,
ResolutionId = resolutionId, ResolutionId = resolutionId,
NormalizeResolution = request.NormalizeResolution, NormalizeResolution = request.NormalizeResolution,
VideoCodec = request.VideoCodec, VideoCodec = request.VideoCodec,

1
ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs

@ -9,6 +9,7 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands
string Name, string Name,
int ThreadCount, int ThreadCount,
bool Transcode, bool Transcode,
bool QsvAcceleration,
int ResolutionId, int ResolutionId,
bool NormalizeResolution, bool NormalizeResolution,
string VideoCodec, string VideoCodec,

1
ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs

@ -35,6 +35,7 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands
p.Name = update.Name; p.Name = update.Name;
p.ThreadCount = update.ThreadCount; p.ThreadCount = update.ThreadCount;
p.Transcode = update.Transcode; p.Transcode = update.Transcode;
p.QsvAcceleration = update.QsvAcceleration;
p.ResolutionId = update.ResolutionId; p.ResolutionId = update.ResolutionId;
p.NormalizeResolution = update.NormalizeResolution; p.NormalizeResolution = update.NormalizeResolution;
p.VideoCodec = update.VideoCodec; p.VideoCodec = update.VideoCodec;

1
ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs

@ -7,6 +7,7 @@ namespace ErsatzTV.Application.FFmpegProfiles
string Name, string Name,
int ThreadCount, int ThreadCount,
bool Transcode, bool Transcode,
bool QsvAcceleration,
ResolutionViewModel Resolution, ResolutionViewModel Resolution,
bool NormalizeResolution, bool NormalizeResolution,
string VideoCodec, string VideoCodec,

1
ErsatzTV.Application/FFmpegProfiles/Mapper.cs

@ -11,6 +11,7 @@ namespace ErsatzTV.Application.FFmpegProfiles
profile.Name, profile.Name,
profile.ThreadCount, profile.ThreadCount,
profile.Transcode, profile.Transcode,
profile.QsvAcceleration,
Project(profile.Resolution), Project(profile.Resolution),
profile.NormalizeResolution, profile.NormalizeResolution,
profile.VideoCodec, profile.VideoCodec,

28
ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsServiceTests.cs

@ -9,6 +9,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
[TestFixture] [TestFixture]
public class FFmpegPlaybackSettingsCalculatorTests public class FFmpegPlaybackSettingsCalculatorTests
{ {
[TestFixture]
public class CalculateSettings public class CalculateSettings
{ {
private readonly FFmpegPlaybackSettingsCalculator _calculator; private readonly FFmpegPlaybackSettingsCalculator _calculator;
@ -732,9 +733,32 @@ namespace ErsatzTV.Core.Tests.FFmpeg
actual.AudioSampleRate.IfNone(0).Should().Be(48); actual.AudioSampleRate.IfNone(0).Should().Be(48);
} }
}
[TestFixture]
public class CalculateSettingsQsv
{
private readonly FFmpegPlaybackSettingsCalculator _calculator;
private FFmpegProfile TestProfile() => public CalculateSettingsQsv() => _calculator = new FFmpegPlaybackSettingsCalculator();
new() { Resolution = new Resolution { Width = 1920, Height = 1080 } };
[Test]
public void Should_UseHardwareAcceleration()
{
FFmpegProfile ffmpegProfile = TestProfile() with { QsvAcceleration = true };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
new MediaVersion(),
DateTimeOffset.Now,
DateTimeOffset.Now);
actual.HardwareAcceleration.Should().Be("qsv");
}
} }
private static FFmpegProfile TestProfile() =>
new() { Resolution = new Resolution { Width = 1920, Height = 1080 } };
} }
} }

1
ErsatzTV.Core/Domain/FFmpegProfile.cs

@ -6,6 +6,7 @@
public string Name { get; set; } public string Name { get; set; }
public int ThreadCount { get; set; } public int ThreadCount { get; set; }
public bool Transcode { get; set; } public bool Transcode { get; set; }
public bool QsvAcceleration { get; set; }
public int ResolutionId { get; set; } public int ResolutionId { get; set; }
public Resolution Resolution { get; set; } public Resolution Resolution { get; set; }
public bool NormalizeResolution { get; set; } public bool NormalizeResolution { get; set; }

2
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettings.cs

@ -9,6 +9,8 @@ namespace ErsatzTV.Core.FFmpeg
{ {
public int ThreadCount { get; set; } public int ThreadCount { get; set; }
public List<string> FormatFlags { get; set; } public List<string> FormatFlags { get; set; }
public string HardwareAcceleration { get; set; }
public string VideoDecoder { get; set; }
public bool RealtimeOutput => true; public bool RealtimeOutput => true;
public Option<TimeSpan> StreamSeek { get; set; } public Option<TimeSpan> StreamSeek { get; set; }
public Option<IDisplaySize> ScaledSize { get; set; } public Option<IDisplaySize> ScaledSize { get; set; }

5
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs

@ -67,6 +67,11 @@ namespace ErsatzTV.Core.FFmpeg
result.Deinterlace = false; result.Deinterlace = false;
break; break;
case StreamingMode.TransportStream: case StreamingMode.TransportStream:
if (ffmpegProfile.QsvAcceleration)
{
result.HardwareAcceleration = "qsv";
}
if (NeedToScale(ffmpegProfile, version)) if (NeedToScale(ffmpegProfile, version))
{ {
IDisplaySize scaledSize = CalculateScaledSize(ffmpegProfile, version); IDisplaySize scaledSize = CalculateScaledSize(ffmpegProfile, version);

39
ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs

@ -31,6 +31,13 @@ namespace ErsatzTV.Core.FFmpeg
{ {
internal class FFmpegProcessBuilder internal class FFmpegProcessBuilder
{ {
private static readonly Dictionary<string, string> QsvMap = new()
{
{ "h264", "h264_qsv" },
{ "hevc", "hevc_qsv" },
{ "mpeg2video", "mpeg2_qsv" }
};
private readonly List<string> _arguments = new(); private readonly List<string> _arguments = new();
private readonly Queue<string> _audioFilters = new(); private readonly Queue<string> _audioFilters = new();
private readonly string _ffmpegPath; private readonly string _ffmpegPath;
@ -45,6 +52,17 @@ namespace ErsatzTV.Core.FFmpeg
return this; return this;
} }
public FFmpegProcessBuilder WithHardwareAcceleration(string hwAccel)
{
if (!string.IsNullOrWhiteSpace(hwAccel))
{
_arguments.Add("-hwaccel");
_arguments.Add(hwAccel);
}
return this;
}
public FFmpegProcessBuilder WithRealtimeOutput(bool realtimeOutput) public FFmpegProcessBuilder WithRealtimeOutput(bool realtimeOutput)
{ {
if (realtimeOutput) if (realtimeOutput)
@ -109,6 +127,19 @@ namespace ErsatzTV.Core.FFmpeg
return this; return this;
} }
public FFmpegProcessBuilder WithInputCodec(string input, string hwAccel, string codec)
{
if (hwAccel == "qsv" && QsvMap.TryGetValue(codec, out string qsvCodec))
{
_arguments.Add("-c:v");
_arguments.Add(qsvCodec);
}
_arguments.Add("-i");
_arguments.Add($"{input}");
return this;
}
public FFmpegProcessBuilder WithFiltergraph(string graph) public FFmpegProcessBuilder WithFiltergraph(string graph)
{ {
_arguments.Add("-vf"); _arguments.Add("-vf");
@ -242,9 +273,13 @@ namespace ErsatzTV.Core.FFmpeg
return this; return this;
} }
public FFmpegProcessBuilder WithScaling(IDisplaySize displaySize, string algorithm) public FFmpegProcessBuilder WithScaling(IDisplaySize displaySize, string hwAccel, string algorithm)
{ {
_videoFilters.Enqueue($"scale={displaySize.Width}:{displaySize.Height}:flags={algorithm}"); _videoFilters.Enqueue(
hwAccel == "qsv"
? $"scale_qsv=w={displaySize.Width}:h={displaySize.Height}"
: $"scale={displaySize.Width}:{displaySize.Height}:flags={algorithm}");
return this; return this;
} }

5
ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs

@ -29,17 +29,18 @@ namespace ErsatzTV.Core.FFmpeg
FFmpegProcessBuilder builder = new FFmpegProcessBuilder(ffmpegPath) FFmpegProcessBuilder builder = new FFmpegProcessBuilder(ffmpegPath)
.WithThreads(playbackSettings.ThreadCount) .WithThreads(playbackSettings.ThreadCount)
.WithHardwareAcceleration(playbackSettings.HardwareAcceleration)
.WithQuiet() .WithQuiet()
.WithFormatFlags(playbackSettings.FormatFlags) .WithFormatFlags(playbackSettings.FormatFlags)
.WithRealtimeOutput(playbackSettings.RealtimeOutput) .WithRealtimeOutput(playbackSettings.RealtimeOutput)
.WithSeek(playbackSettings.StreamSeek) .WithSeek(playbackSettings.StreamSeek)
.WithInput(path); .WithInputCodec(path, playbackSettings.HardwareAcceleration, version.VideoCodec);
playbackSettings.ScaledSize.Match( playbackSettings.ScaledSize.Match(
scaledSize => scaledSize =>
{ {
builder = builder.WithDeinterlace(playbackSettings.Deinterlace) builder = builder.WithDeinterlace(playbackSettings.Deinterlace)
.WithScaling(scaledSize, playbackSettings.ScalingAlgorithm) .WithScaling(scaledSize, playbackSettings.HardwareAcceleration, playbackSettings.ScalingAlgorithm)
.WithSAR(); .WithSAR();
scaledSize = scaledSize.PadToEven(); scaledSize = scaledSize.PadToEven();

1494
ErsatzTV.Infrastructure/Migrations/20210302122352_Add_FFmpegProfileQsvAcceleration.Designer.cs generated

File diff suppressed because it is too large Load Diff

20
ErsatzTV.Infrastructure/Migrations/20210302122352_Add_FFmpegProfileQsvAcceleration.cs

@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_FFmpegProfileQsvAcceleration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder) =>
migrationBuilder.AddColumn<bool>(
"QsvAcceleration",
"FFmpegProfile",
"INTEGER",
nullable: false,
defaultValue: false);
protected override void Down(MigrationBuilder migrationBuilder) =>
migrationBuilder.DropColumn(
"QsvAcceleration",
"FFmpegProfile");
}
}

3
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -246,6 +246,9 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<bool>("NormalizeVideoCodec") b.Property<bool>("NormalizeVideoCodec")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("QsvAcceleration")
.HasColumnType("INTEGER");
b.Property<int>("ResolutionId") b.Property<int>("ResolutionId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");

3
ErsatzTV/Pages/FFmpegEditor.razor

@ -49,6 +49,9 @@
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudTextField Disabled="@(!_model.Transcode)" Label="Buffer Size" @bind-Value="_model.VideoBufferSize" For="@(() => _model.VideoBufferSize)" Adornment="Adornment.End" AdornmentText="kBit"/> <MudTextField Disabled="@(!_model.Transcode)" Label="Buffer Size" @bind-Value="_model.VideoBufferSize" For="@(() => _model.VideoBufferSize)" Adornment="Adornment.End" AdornmentText="kBit"/>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3">
<MudCheckBox Label="QSV Acceleration" @bind-Checked="@_model.QsvAcceleration" For="@(() => _model.QsvAcceleration)"/>
</MudElement>
</MudItem> </MudItem>
<MudItem> <MudItem>
<MudText Typo="Typo.h6">Audio</MudText> <MudText Typo="Typo.h6">Audio</MudText>

8
ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs

@ -23,6 +23,14 @@ namespace ErsatzTV.Validators
RuleFor(x => x.AudioVolume).GreaterThanOrEqualTo(0); RuleFor(x => x.AudioVolume).GreaterThanOrEqualTo(0);
RuleFor(x => x.AudioChannels).GreaterThan(0); RuleFor(x => x.AudioChannels).GreaterThan(0);
}); });
When(
x => x.QsvAcceleration,
() =>
{
RuleFor(x => x.VideoCodec).Must(c => c.EndsWith("_qsv"))
.WithMessage("QSV codec is required (h264_qsv, hevc_qsv, mpeg2_qsv)");
});
} }
} }
} }

4
ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

@ -27,6 +27,7 @@ namespace ErsatzTV.ViewModels
Resolution = viewModel.Resolution; Resolution = viewModel.Resolution;
ThreadCount = viewModel.ThreadCount; ThreadCount = viewModel.ThreadCount;
Transcode = viewModel.Transcode; Transcode = viewModel.Transcode;
QsvAcceleration = viewModel.QsvAcceleration;
VideoBitrate = viewModel.VideoBitrate; VideoBitrate = viewModel.VideoBitrate;
VideoBufferSize = viewModel.VideoBufferSize; VideoBufferSize = viewModel.VideoBufferSize;
VideoCodec = viewModel.VideoCodec; VideoCodec = viewModel.VideoCodec;
@ -47,6 +48,7 @@ namespace ErsatzTV.ViewModels
public ResolutionViewModel Resolution { get; set; } public ResolutionViewModel Resolution { get; set; }
public int ThreadCount { get; set; } public int ThreadCount { get; set; }
public bool Transcode { get; set; } public bool Transcode { get; set; }
public bool QsvAcceleration { get; set; }
public int VideoBitrate { get; set; } public int VideoBitrate { get; set; }
public int VideoBufferSize { get; set; } public int VideoBufferSize { get; set; }
public string VideoCodec { get; set; } public string VideoCodec { get; set; }
@ -56,6 +58,7 @@ namespace ErsatzTV.ViewModels
Name, Name,
ThreadCount, ThreadCount,
Transcode, Transcode,
QsvAcceleration,
Resolution.Id, Resolution.Id,
NormalizeResolution, NormalizeResolution,
VideoCodec, VideoCodec,
@ -78,6 +81,7 @@ namespace ErsatzTV.ViewModels
Name, Name,
ThreadCount, ThreadCount,
Transcode, Transcode,
QsvAcceleration,
Resolution.Id, Resolution.Id,
NormalizeResolution, NormalizeResolution,
VideoCodec, VideoCodec,

Loading…
Cancel
Save