Browse Source

qsv and pts fixes (#2184)

* try to fix qsv freezing

* update changelog

* fix unit tests
pull/2186/head
Jason Dove 4 weeks ago committed by GitHub
parent
commit
89b495dc90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 5
      ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs
  3. 4
      ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs
  4. 8
      ErsatzTV.FFmpeg/Filter/AudioSetPtsFilter.cs
  5. 8
      ErsatzTV.FFmpeg/Filter/VideoSetPtsFilter.cs
  6. 8
      ErsatzTV.FFmpeg/FilterChain.cs
  7. 2
      ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs
  8. 16
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  9. 2
      ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs
  10. 6
      ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs
  11. 2
      ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs

2
CHANGELOG.md

@ -159,6 +159,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix block playout EPG generation to use `XMLTV Time Zone` setting - Fix block playout EPG generation to use `XMLTV Time Zone` setting
- Fix adding "official" Trakt lists - Fix adding "official" Trakt lists
- Fix searching for `collection` names with spaces or other special characters, e.g. `collection:"Movies - Action"` - Fix searching for `collection` names with spaces or other special characters, e.g. `collection:"Movies - Action"`
- Fix QSV transcoding errors when scaling
- Fix QSV frame freezing in browser
## [25.2.0] - 2025-06-24 ## [25.2.0] - 2025-06-24
### Added ### Added

5
ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs

@ -101,7 +101,10 @@ public class PrepareTroubleshootingPlaybackHandler(
if (!hlsRealtime && !request.StartFromBeginning) if (!hlsRealtime && !request.StartFromBeginning)
{ {
inPoint = TimeSpan.FromSeconds(version.Duration.TotalSeconds / 2.0); inPoint = TimeSpan.FromSeconds(version.Duration.TotalSeconds / 2.0);
duration = TimeSpan.FromSeconds(duration.TotalSeconds / 2.0); if (inPoint.TotalSeconds < 30)
{
duration = inPoint;
}
outPoint = inPoint + duration; outPoint = inPoint + duration;
} }

4
ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs

@ -116,7 +116,7 @@ public class PipelineBuilderBaseTests
string command = PrintCommand(videoInputFile, audioInputFile, None, None, result); string command = PrintCommand(videoInputFile, audioInputFile, None, None, result);
command.ShouldBe( command.ShouldBe(
"-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -readrate 1.0 -i /tmp/whatever.mkv -filter_complex [0:1]aresample=async=1:first_pts=0[a] -map 0:0 -map [a] -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -bf 0 -sc_threshold 0 -video_track_timescale 90000 -b:v 2000k -maxrate:v 2000k -bufsize:v 4000k -c:v libx265 -tag:v hvc1 -x265-params log-level=error -c:a aac -b:a 320k -maxrate:a 320k -bufsize:a 640k -ar 48k -f mpegts -mpegts_flags +initial_discontinuity pipe:1"); "-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -readrate 1.0 -i /tmp/whatever.mkv -filter_complex [0:1]aresample=async=1:first_pts=0,asetpts=PTS-STARTPTS[a];[0:0]setpts=PTS-STARTPTS[vpf] -map [vpf] -map [a] -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -bf 0 -sc_threshold 0 -video_track_timescale 90000 -b:v 2000k -maxrate:v 2000k -bufsize:v 4000k -c:v libx265 -tag:v hvc1 -x265-params log-level=error -c:a aac -b:a 320k -maxrate:a 320k -bufsize:a 640k -ar 48k -f mpegts -mpegts_flags +initial_discontinuity pipe:1");
} }
[Test] [Test]
@ -213,7 +213,7 @@ public class PipelineBuilderBaseTests
string command = PrintCommand(videoInputFile, audioInputFile, None, None, result); string command = PrintCommand(videoInputFile, audioInputFile, None, None, result);
command.ShouldBe( command.ShouldBe(
"-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -readrate 1.0 -i /tmp/whatever.mkv -filter_complex [0:1]aresample=async=1:first_pts=0[a] -map 0:0 -map [a] -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -bf 0 -sc_threshold 0 -video_track_timescale 90000 -b:v 2000k -maxrate:v 2000k -bufsize:v 4000k -c:v libx265 -tag:v hvc1 -x265-params log-level=error -c:a aac -ac 6 -b:a 320k -maxrate:a 320k -bufsize:a 640k -ar 48k -f mpegts -mpegts_flags +initial_discontinuity pipe:1"); "-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -readrate 1.0 -i /tmp/whatever.mkv -filter_complex [0:1]aresample=async=1:first_pts=0,asetpts=PTS-STARTPTS[a];[0:0]setpts=PTS-STARTPTS[vpf] -map [vpf] -map [a] -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -bf 0 -sc_threshold 0 -video_track_timescale 90000 -b:v 2000k -maxrate:v 2000k -bufsize:v 4000k -c:v libx265 -tag:v hvc1 -x265-params log-level=error -c:a aac -ac 6 -b:a 320k -maxrate:a 320k -bufsize:a 640k -ar 48k -f mpegts -mpegts_flags +initial_discontinuity pipe:1");
} }
[Test] [Test]

8
ErsatzTV.FFmpeg/Filter/AudioSetPtsFilter.cs

@ -0,0 +1,8 @@
namespace ErsatzTV.FFmpeg.Filter;
public class AudioSetPtsFilter : BaseFilter
{
public override string Filter => "asetpts=PTS-STARTPTS";
public override FrameState NextState(FrameState currentState) => currentState;
}

8
ErsatzTV.FFmpeg/Filter/VideoSetPtsFilter.cs

@ -0,0 +1,8 @@
namespace ErsatzTV.FFmpeg.Filter;
public class VideoSetPtsFilter : BaseFilter
{
public override string Filter => "setpts=PTS-STARTPTS";
public override FrameState NextState(FrameState currentState) => currentState;
}

8
ErsatzTV.FFmpeg/FilterChain.cs

@ -8,11 +8,5 @@ public record FilterChain(
List<IPipelineFilterStep> SubtitleOverlayFilterSteps, List<IPipelineFilterStep> SubtitleOverlayFilterSteps,
List<IPipelineFilterStep> PixelFormatFilterSteps) List<IPipelineFilterStep> PixelFormatFilterSteps)
{ {
public static readonly FilterChain Empty = new( public static readonly FilterChain Empty = new([], [], [], [], [], []);
new List<IPipelineFilterStep>(),
new List<IPipelineFilterStep>(),
new List<IPipelineFilterStep>(),
new List<IPipelineFilterStep>(),
new List<IPipelineFilterStep>(),
new List<IPipelineFilterStep>());
} }

2
ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs

@ -306,6 +306,8 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
context, context,
pipelineSteps); pipelineSteps);
pixelFormatFilterSteps.Add(new VideoSetPtsFilter());
return new FilterChain( return new FilterChain(
videoInputFile.FilterSteps, videoInputFile.FilterSteps,
watermarkInputFile.Map(wm => wm.FilterSteps).IfNone(new List<IPipelineFilterStep>()), watermarkInputFile.Map(wm => wm.FilterSteps).IfNone(new List<IPipelineFilterStep>()),

16
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -327,6 +327,9 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
{ {
foreach (string segmentTemplate in ffmpegState.HlsSegmentTemplate) foreach (string segmentTemplate in ffmpegState.HlsSegmentTemplate)
{ {
//bool oneSecondGop = ffmpegState.EncoderHardwareAccelerationMode is HardwareAccelerationMode.Qsv;
var oneSecondGop = false;
pipelineSteps.Add( pipelineSteps.Add(
new OutputFormatHls( new OutputFormatHls(
desiredState, desiredState,
@ -334,7 +337,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
segmentTemplate, segmentTemplate,
playlistPath, playlistPath,
ffmpegState.PtsOffset == 0, ffmpegState.PtsOffset == 0,
ffmpegState.EncoderHardwareAccelerationMode is HardwareAccelerationMode.Qsv, oneSecondGop,
ffmpegState.IsTroubleshooting)); ffmpegState.IsTroubleshooting));
} }
} }
@ -412,16 +415,21 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
SetAudioPad(audioInputFile, pipelineSteps); SetAudioPad(audioInputFile, pipelineSteps);
} }
private void SetAudioPad(AudioInputFile audioInputFile, List<IPipelineStep> pipelineSteps) private static void SetAudioPad(AudioInputFile audioInputFile, List<IPipelineStep> pipelineSteps)
{ {
if (pipelineSteps.All(ps => ps is not EncoderCopyAudio)) if (pipelineSteps.All(ps => ps is not EncoderCopyAudio))
{ {
_audioInputFile.Iter(f => f.FilterSteps.Add(new AudioFirstPtsFilter(0))); audioInputFile.FilterSteps.Add(new AudioFirstPtsFilter(0));
} }
foreach (TimeSpan _ in audioInputFile.DesiredState.AudioDuration) foreach (TimeSpan _ in audioInputFile.DesiredState.AudioDuration)
{ {
_audioInputFile.Iter(f => f.FilterSteps.Add(new AudioPadFilter())); audioInputFile.FilterSteps.Add(new AudioPadFilter());
}
if (pipelineSteps.All(ps => ps is not EncoderCopyAudio))
{
audioInputFile.FilterSteps.Add(new AudioSetPtsFilter());
} }
} }

2
ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs

@ -248,6 +248,8 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
context, context,
pipelineSteps); pipelineSteps);
pixelFormatFilterSteps.Add(new VideoSetPtsFilter());
return new FilterChain( return new FilterChain(
videoInputFile.FilterSteps, videoInputFile.FilterSteps,
watermarkInputFile.Map(wm => wm.FilterSteps).IfNone(new List<IPipelineFilterStep>()), watermarkInputFile.Map(wm => wm.FilterSteps).IfNone(new List<IPipelineFilterStep>()),

6
ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs

@ -148,10 +148,12 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase
currentState, currentState,
pipelineSteps); pipelineSteps);
pixelFormatFilterSteps.Add(new VideoSetPtsFilter());
return new FilterChain( return new FilterChain(
videoInputFile.FilterSteps, videoInputFile.FilterSteps,
watermarkInputFile.Map(wm => wm.FilterSteps).IfNone(new List<IPipelineFilterStep>()), watermarkInputFile.Map(wm => wm.FilterSteps).IfNone([]),
subtitleInputFile.Map(st => st.FilterSteps).IfNone(new List<IPipelineFilterStep>()), subtitleInputFile.Map(st => st.FilterSteps).IfNone([]),
watermarkOverlayFilterSteps, watermarkOverlayFilterSteps,
subtitleOverlayFilterSteps, subtitleOverlayFilterSteps,
pixelFormatFilterSteps); pixelFormatFilterSteps);

2
ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs

@ -260,6 +260,8 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
currentState, currentState,
pipelineSteps); pipelineSteps);
pixelFormatFilterSteps.Add(new VideoSetPtsFilter());
return new FilterChain( return new FilterChain(
videoInputFile.FilterSteps, videoInputFile.FilterSteps,
watermarkInputFile.Map(wm => wm.FilterSteps).IfNone(new List<IPipelineFilterStep>()), watermarkInputFile.Map(wm => wm.FilterSteps).IfNone(new List<IPipelineFilterStep>()),

Loading…
Cancel
Save