Browse Source

check for multiple h264 profiles using qsv decoding

pull/2829/head
Jason Dove 6 months ago
parent
commit
51d1f2730a
No known key found for this signature in database
  1. 28
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  2. 5
      ErsatzTV.Core/Interfaces/Metadata/ILocalStatisticsProvider.cs
  3. 3
      ErsatzTV.FFmpeg/MediaStream.cs
  4. 8
      ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs
  5. 58
      ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs

28
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -233,6 +233,16 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
scanKind = await ProbeScanKind(ffmpegPath, videoVersion.MediaItem, cancellationToken); scanKind = await ProbeScanKind(ffmpegPath, videoVersion.MediaItem, cancellationToken);
} }
HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode(playbackSettings);
// QSV may have sync issues with h264 files that have multiple profiles
// check and flag here so software decoding can be used if needed
bool hasMultipleProfiles = false;
if (hwAccel is HardwareAccelerationMode.Qsv && videoStream.Codec is VideoFormat.H264)
{
hasMultipleProfiles = await ProbeHasMultipleProfiles(ffmpegPath, videoVersion.MediaItem, cancellationToken);
}
var ffmpegVideoStream = new VideoStream( var ffmpegVideoStream = new VideoStream(
videoStream.Index, videoStream.Index,
videoStream.Codec, videoStream.Codec,
@ -248,7 +258,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
videoVersion.MediaVersion.DisplayAspectRatio, videoVersion.MediaVersion.DisplayAspectRatio,
new FrameRate(videoVersion.MediaVersion.RFrameRate), new FrameRate(videoVersion.MediaVersion.RFrameRate),
videoPath != audioPath, // still image when paths are different videoPath != audioPath, // still image when paths are different
scanKind); scanKind,
hasMultipleProfiles);
var videoInputFile = new VideoInputFile( var videoInputFile = new VideoInputFile(
videoPath, videoPath,
@ -405,8 +416,6 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
graphicsElementContexts.AddRange(watermarks.Map(wm => new WatermarkElementContext(wm))); graphicsElementContexts.AddRange(watermarks.Map(wm => new WatermarkElementContext(wm)));
} }
HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode(playbackSettings);
string videoFormat = GetVideoFormat(playbackSettings); string videoFormat = GetVideoFormat(playbackSettings);
Option<string> maybeVideoProfile = GetVideoProfile(videoFormat, channel.FFmpegProfile.VideoProfile); Option<string> maybeVideoProfile = GetVideoProfile(videoFormat, channel.FFmpegProfile.VideoProfile);
Option<string> maybeVideoPreset = GetVideoPreset( Option<string> maybeVideoPreset = GetVideoPreset(
@ -652,6 +661,19 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
return result; return result;
} }
private async Task<bool> ProbeHasMultipleProfiles(
string ffmpegPath,
MediaItem mediaItem,
CancellationToken cancellationToken)
{
_logger.LogDebug("Will probe for h264 profile count");
Option<int> profileCount =
await _localStatisticsProvider.GetProfileCount(ffmpegPath, mediaItem, cancellationToken);
return await profileCount.IfNoneAsync(1) > 1;
}
public async Task<Command> ForError( public async Task<Command> ForError(
string ffmpegPath, string ffmpegPath,
Channel channel, Channel channel,

5
ErsatzTV.Core/Interfaces/Metadata/ILocalStatisticsProvider.cs

@ -14,4 +14,9 @@ public interface ILocalStatisticsProvider
string ffmpegPath, string ffmpegPath,
MediaItem mediaItem, MediaItem mediaItem,
CancellationToken cancellationToken); CancellationToken cancellationToken);
Task<Option<int>> GetProfileCount(
string ffmpegPath,
MediaItem mediaItem,
CancellationToken cancellationToken);
} }

3
ErsatzTV.FFmpeg/MediaStream.cs

@ -25,7 +25,8 @@ public record VideoStream(
string DisplayAspectRatio, string DisplayAspectRatio,
Option<FrameRate> FrameRate, Option<FrameRate> FrameRate,
bool StillImage, bool StillImage,
ScanKind ScanKind) : MediaStream( ScanKind ScanKind,
bool HasMultipleProfiles) : MediaStream(
Index, Index,
Codec, Codec,
StreamKind.Video) StreamKind.Video)

8
ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs

@ -91,6 +91,14 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
decodeCapability = FFmpegCapability.Software; decodeCapability = FFmpegCapability.Software;
} }
// QSV cannot always decode properly when *not* seeking, so check if software is needed
if (decodeCapability == FFmpegCapability.Hardware && ffmpegState.Start.Filter(s => s > TimeSpan.Zero).IsNone &&
videoStream.HasMultipleProfiles)
{
_logger.LogDebug("Forcing software decode when input file uses multiple h264 profiles");
decodeCapability = FFmpegCapability.Software;
}
// give a bogus value so no cuda devices are visible to ffmpeg // give a bogus value so no cuda devices are visible to ffmpeg
pipelineSteps.Add(new CudaVisibleDevicesVariable("999")); pipelineSteps.Add(new CudaVisibleDevicesVariable("999"));

58
ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs

@ -181,6 +181,24 @@ public partial class LocalStatisticsProvider : ILocalStatisticsProvider
return Option<double>.None; return Option<double>.None;
} }
public async Task<Option<int>> GetProfileCount(
string ffmpegPath,
MediaItem mediaItem,
CancellationToken cancellationToken)
{
try
{
string filePath = await PathForMediaItem(mediaItem);
return await GetProfileCount(ffmpegPath, filePath);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to check profile count for media item {Id}", mediaItem.Id);
}
return Option<int>.None;
}
private async Task<Either<BaseError, bool>> RefreshStatistics( private async Task<Either<BaseError, bool>> RefreshStatistics(
string ffmpegPath, string ffmpegPath,
string ffprobePath, string ffprobePath,
@ -336,6 +354,46 @@ public partial class LocalStatisticsProvider : ILocalStatisticsProvider
return stats; return stats;
} }
private async Task<Option<int>> GetProfileCount(string ffmpegPath, string filePath)
{
string[] arguments =
[
"-hide_banner",
"-i", filePath,
"-c", "copy",
"-bsf:v", "trace_headers",
"-f", "null", "-"
];
var uniqueProfiles = new System.Collections.Generic.HashSet<string>();
CommandResult traceHeaders = await Cli.Wrap(ffmpegPath)
.WithArguments(arguments)
.WithValidation(CommandResultValidation.None)
.WithStandardErrorPipe(PipeTarget.ToDelegate(line =>
{
int idx = line.IndexOf("profile_idc", StringComparison.Ordinal);
if (idx >= 0)
{
uniqueProfiles.Add(line[idx..]);
}
}))
.ExecuteAsync();
if (traceHeaders.ExitCode != 0)
{
_logger.LogInformation(
"FFmpeg trace headers with arguments {Arguments} exited with code {ExitCode}",
arguments,
traceHeaders.ExitCode);
return Option<int>.None;
}
return uniqueProfiles.Count;
}
private async Task AnalyzeDuration(string ffmpegPath, string path, MediaVersion version) private async Task AnalyzeDuration(string ffmpegPath, string path, MediaVersion version)
{ {
try try

Loading…
Cancel
Save