Browse Source

add picture subtitle transcoding tests, and make them all pass with nvenc (#734)

pull/735/head
Jason Dove 4 years ago committed by GitHub
parent
commit
765df64555
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 6
      ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj
  3. 72
      ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs
  4. BIN
      ErsatzTV.Core.Tests/Resources/test.sup
  5. 7
      ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs
  6. 18
      ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs
  7. 18
      ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs
  8. 2
      ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj
  9. 1
      ErsatzTV.FFmpeg/PipelineBuilder.cs

2
CHANGELOG.md

@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Fixed
- Fix subtitles edge case with NVENC
## [0.5.0-beta] - 2022-04-13 ## [0.5.0-beta] - 2022-04-13
### Fixed ### Fixed

6
ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj

@ -8,9 +8,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Bugsnag" Version="3.0.1" /> <PackageReference Include="Bugsnag" Version="3.0.1" />
<PackageReference Include="CliWrap" Version="3.4.2" /> <PackageReference Include="CliWrap" Version="3.4.1" />
<PackageReference Include="FluentAssertions" Version="6.6.0" /> <PackageReference Include="FluentAssertions" Version="6.6.0" />
<PackageReference Include="LanguageExt.Core" Version="4.0.4" /> <PackageReference Include="LanguageExt.Core" Version="4.0.4" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
@ -36,6 +37,9 @@
<Content Include="Resources\ErsatzTV.png"> <Content Include="Resources\ErsatzTV.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Resources\test.sup">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
</Project> </Project>

72
ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs

@ -65,6 +65,12 @@ public class TranscodingTests
// TODO: animated vs static // TODO: animated vs static
} }
public enum Subtitle
{
None,
Picture
}
private class TestData private class TestData
{ {
public static Watermark[] Watermarks = public static Watermark[] Watermarks =
@ -73,6 +79,12 @@ public class TranscodingTests
Watermark.PermanentOpaque, Watermark.PermanentOpaque,
Watermark.PermanentTransparent Watermark.PermanentTransparent
}; };
public static Subtitle[] Subtitles =
{
Subtitle.None,
Subtitle.Picture
};
public static Padding[] Paddings = public static Padding[] Paddings =
{ {
@ -158,6 +170,7 @@ public class TranscodingTests
[ValueSource(typeof(TestData), nameof(TestData.Paddings))] Padding padding, [ValueSource(typeof(TestData), nameof(TestData.Paddings))] Padding padding,
[ValueSource(typeof(TestData), nameof(TestData.VideoScanKinds))] VideoScanKind videoScanKind, [ValueSource(typeof(TestData), nameof(TestData.VideoScanKinds))] VideoScanKind videoScanKind,
[ValueSource(typeof(TestData), nameof(TestData.Watermarks))] Watermark watermark, [ValueSource(typeof(TestData), nameof(TestData.Watermarks))] Watermark watermark,
[ValueSource(typeof(TestData), nameof(TestData.Subtitles))] Subtitle subtitle,
[ValueSource(typeof(TestData), nameof(TestData.VideoFormats))] FFmpegProfileVideoFormat profileVideoFormat, [ValueSource(typeof(TestData), nameof(TestData.VideoFormats))] FFmpegProfileVideoFormat profileVideoFormat,
// [ValueSource(typeof(TestData), nameof(TestData.NoAcceleration))] HardwareAccelerationKind profileAcceleration) // [ValueSource(typeof(TestData), nameof(TestData.NoAcceleration))] HardwareAccelerationKind profileAcceleration)
[ValueSource(typeof(TestData), nameof(TestData.NvidiaAcceleration))] HardwareAccelerationKind profileAcceleration) [ValueSource(typeof(TestData), nameof(TestData.NvidiaAcceleration))] HardwareAccelerationKind profileAcceleration)
@ -175,7 +188,7 @@ public class TranscodingTests
} }
string name = GetStringSha256Hash( string name = GetStringSha256Hash(
$"{inputFormat.Encoder}_{inputFormat.PixelFormat}_{videoScanKind}_{padding}_{profileResolution}_{profileVideoFormat}_{profileAcceleration}"); $"{inputFormat.Encoder}_{inputFormat.PixelFormat}_{videoScanKind}_{padding}_{watermark}_{subtitle}_{profileResolution}_{profileVideoFormat}_{profileAcceleration}");
string file = Path.Combine(TestContext.CurrentContext.TestDirectory, $"{name}.mkv"); string file = Path.Combine(TestContext.CurrentContext.TestDirectory, $"{name}.mkv");
if (!File.Exists(file)) if (!File.Exists(file))
@ -203,6 +216,46 @@ public class TranscodingTests
// ReSharper disable once MethodHasAsyncOverload // ReSharper disable once MethodHasAsyncOverload
p1.WaitForExit(); p1.WaitForExit();
p1.ExitCode.Should().Be(0); p1.ExitCode.Should().Be(0);
switch (subtitle)
{
case Subtitle.Picture:
string sourceFile = Path.GetTempFileName() + ".mkv";
File.Move(file, sourceFile, true);
string tempFileName = Path.GetTempFileName() + ".mkv";
string subPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "test.sup");
var p2 = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = ExecutableName("mkvmerge"),
Arguments = $"-o {tempFileName} {sourceFile} {subPath}"
}
};
p2.Start();
await p2.WaitForExitAsync();
// ReSharper disable once MethodHasAsyncOverload
p2.WaitForExit();
if (p2.ExitCode != 0)
{
if (File.Exists(sourceFile))
{
File.Delete(sourceFile);
}
if (File.Exists(file))
{
File.Delete(file);
}
}
p2.ExitCode.Should().Be(0);
File.Move(tempFileName, file, true);
break;
}
} }
var imageCache = new Mock<IImageCache>(); var imageCache = new Mock<IImageCache>();
@ -221,7 +274,7 @@ public class TranscodingTests
imageCache.Object, imageCache.Object,
new Mock<ITempFilePool>().Object, new Mock<ITempFilePool>().Object,
new Mock<IClient>().Object, new Mock<IClient>().Object,
new Mock<IMemoryCache>().Object, new MemoryCache(new MemoryCacheOptions()),
LoggerFactory.CreateLogger<FFmpegProcessService>()); LoggerFactory.CreateLogger<FFmpegProcessService>());
var service = new FFmpegLibraryProcessService( var service = new FFmpegLibraryProcessService(
@ -318,6 +371,12 @@ public class TranscodingTests
break; break;
} }
ChannelSubtitleMode subtitleMode = subtitle switch
{
Subtitle.Picture => ChannelSubtitleMode.Any,
_ => ChannelSubtitleMode.None
};
using Process process = await service.ForPlayoutItem( using Process process = await service.ForPlayoutItem(
ExecutableName("ffmpeg"), ExecutableName("ffmpeg"),
ExecutableName("ffprobe"), ExecutableName("ffprobe"),
@ -329,9 +388,11 @@ public class TranscodingTests
{ {
HardwareAcceleration = profileAcceleration, HardwareAcceleration = profileAcceleration,
VideoFormat = profileVideoFormat, VideoFormat = profileVideoFormat,
AudioFormat = FFmpegProfileAudioFormat.Aac AudioFormat = FFmpegProfileAudioFormat.Aac,
DeinterlaceVideo = true
}, },
StreamingMode = StreamingMode.TransportStream StreamingMode = StreamingMode.TransportStream,
SubtitleMode = subtitleMode
}, },
v, v,
v, v,
@ -368,6 +429,7 @@ public class TranscodingTests
result = await Cli.Wrap(process.StartInfo.FileName) result = await Cli.Wrap(process.StartInfo.FileName)
.WithArguments(process.StartInfo.ArgumentList) .WithArguments(process.StartInfo.ArgumentList)
.WithValidation(CommandResultValidation.None) .WithValidation(CommandResultValidation.None)
.WithStandardOutputPipe(PipeTarget.Null)
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(sb)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(sb))
.ExecuteAsync(timeoutSignal.Token); .ExecuteAsync(timeoutSignal.Token);
} }
@ -425,7 +487,7 @@ public class TranscodingTests
Optional(version.Streams.First(s => s.MediaStreamKind == MediaStreamKind.Audio)).AsTask(); Optional(version.Streams.First(s => s.MediaStreamKind == MediaStreamKind.Audio)).AsTask();
public Task<Option<MediaStream>> SelectSubtitleStream(Channel channel, MediaVersion version) => public Task<Option<MediaStream>> SelectSubtitleStream(Channel channel, MediaVersion version) =>
Optional(version.Streams.First(s => s.MediaStreamKind == MediaStreamKind.Subtitle)).AsTask(); Optional(version.Streams.Find(s => s.MediaStreamKind == MediaStreamKind.Subtitle)).AsTask();
} }
private static string ExecutableName(string baseName) => private static string ExecutableName(string baseName) =>

BIN
ErsatzTV.Core.Tests/Resources/test.sup

Binary file not shown.

7
ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs

@ -15,15 +15,18 @@ public static class AvailableEncoders
FrameState currentState, FrameState currentState,
FrameState desiredState, FrameState desiredState,
Option<WatermarkInputFile> maybeWatermarkInputFile, Option<WatermarkInputFile> maybeWatermarkInputFile,
Option<SubtitleInputFile> maybeSubtitleInputFile,
ILogger logger) => ILogger logger) =>
(ffmpegState.HardwareAccelerationMode, desiredState.VideoFormat) switch (ffmpegState.HardwareAccelerationMode, desiredState.VideoFormat) switch
{ {
(HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) => new EncoderHevcNvenc( (HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) => new EncoderHevcNvenc(
currentState, currentState,
maybeWatermarkInputFile), maybeWatermarkInputFile,
maybeSubtitleInputFile),
(HardwareAccelerationMode.Nvenc, VideoFormat.H264) => new EncoderH264Nvenc( (HardwareAccelerationMode.Nvenc, VideoFormat.H264) => new EncoderH264Nvenc(
currentState, currentState,
maybeWatermarkInputFile), maybeWatermarkInputFile,
maybeSubtitleInputFile),
(HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv( (HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv(
currentState, currentState,

18
ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs

@ -6,11 +6,16 @@ public class EncoderH264Nvenc : EncoderBase
{ {
private readonly FrameState _currentState; private readonly FrameState _currentState;
private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile; private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile;
private readonly Option<SubtitleInputFile> _maybeSubtitleInputFile;
public EncoderH264Nvenc(FrameState currentState, Option<WatermarkInputFile> maybeWatermarkInputFile) public EncoderH264Nvenc(
FrameState currentState,
Option<WatermarkInputFile> maybeWatermarkInputFile,
Option<SubtitleInputFile> maybeSubtitleInputFile)
{ {
_currentState = currentState; _currentState = currentState;
_maybeWatermarkInputFile = maybeWatermarkInputFile; _maybeWatermarkInputFile = maybeWatermarkInputFile;
_maybeSubtitleInputFile = maybeSubtitleInputFile;
} }
public override FrameState NextState(FrameState currentState) => currentState with public override FrameState NextState(FrameState currentState) => currentState with
@ -27,10 +32,15 @@ public class EncoderH264Nvenc : EncoderBase
{ {
get get
{ {
// only upload to hw if we need to overlay a watermark // only upload to hw if we need to overlay (watermark or subtitle)
if (_maybeWatermarkInputFile.IsSome && _currentState.FrameDataLocation == FrameDataLocation.Software) if (_currentState.FrameDataLocation == FrameDataLocation.Software)
{ {
return "hwupload_cuda"; bool isPictureSubtitle = _maybeSubtitleInputFile.Map(s => s.IsImageBased).IfNone(false);
if (isPictureSubtitle || _maybeWatermarkInputFile.IsSome)
{
return "hwupload_cuda";
}
} }
return string.Empty; return string.Empty;

18
ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs

@ -6,11 +6,16 @@ public class EncoderHevcNvenc : EncoderBase
{ {
private readonly FrameState _currentState; private readonly FrameState _currentState;
private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile; private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile;
private readonly Option<SubtitleInputFile> _maybeSubtitleInputFile;
public EncoderHevcNvenc(FrameState currentState, Option<WatermarkInputFile> maybeWatermarkInputFile) public EncoderHevcNvenc(
FrameState currentState,
Option<WatermarkInputFile> maybeWatermarkInputFile,
Option<SubtitleInputFile> maybeSubtitleInputFile)
{ {
_currentState = currentState; _currentState = currentState;
_maybeWatermarkInputFile = maybeWatermarkInputFile; _maybeWatermarkInputFile = maybeWatermarkInputFile;
_maybeSubtitleInputFile = maybeSubtitleInputFile;
} }
public override FrameState NextState(FrameState currentState) => currentState with public override FrameState NextState(FrameState currentState) => currentState with
@ -27,10 +32,15 @@ public class EncoderHevcNvenc : EncoderBase
{ {
get get
{ {
// only upload to hw if we need to overlay a watermark // only upload to hw if we need to overlay (watermark or subtitle)
if (_maybeWatermarkInputFile.IsSome && _currentState.FrameDataLocation == FrameDataLocation.Software) if (_currentState.FrameDataLocation == FrameDataLocation.Software)
{ {
return "hwupload_cuda"; bool isPictureSubtitle = _maybeSubtitleInputFile.Map(s => s.IsImageBased).IfNone(false);
if (isPictureSubtitle || _maybeWatermarkInputFile.IsSome)
{
return "hwupload_cuda";
}
} }
return string.Empty; return string.Empty;

2
ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj

@ -7,7 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CliWrap" Version="3.4.2" /> <PackageReference Include="CliWrap" Version="3.4.1" />
<PackageReference Include="LanguageExt.Core" Version="4.0.4" /> <PackageReference Include="LanguageExt.Core" Version="4.0.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
</ItemGroup> </ItemGroup>

1
ErsatzTV.FFmpeg/PipelineBuilder.cs

@ -474,6 +474,7 @@ public class PipelineBuilder
currentState, currentState,
desiredState, desiredState,
_watermarkInputFile, _watermarkInputFile,
_subtitleInputFile,
_logger)) _logger))
{ {
encoder = e; encoder = e;

Loading…
Cancel
Save