Browse Source

audio and video normalization settings appear to work

pull/2802/head
Jason Dove 6 months ago
parent
commit
b8e44f7544
No known key found for this signature in database
  1. 3
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs
  2. 2
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
  3. 4
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  4. 20
      ErsatzTV.FFmpeg/OutputFormat/OutputFormatHls.cs
  5. 38
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  6. 30
      ErsatzTV/Pages/Channels.razor
  7. 33
      ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

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

@ -1,7 +1,6 @@ @@ -1,7 +1,6 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Search;
using ErsatzTV.FFmpeg.Pipeline;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
@ -87,7 +86,7 @@ public class CreateFFmpegProfileHandler : @@ -87,7 +86,7 @@ public class CreateFFmpegProfileHandler :
VideoBitrate = request.VideoBitrate,
VideoBufferSize = request.VideoBufferSize,
TonemapAlgorithm = request.TonemapAlgorithm,
AudioFormat = request.AudioFormat,
AudioFormat = request.NormalizeAudio ? request.AudioFormat : FFmpegProfileAudioFormat.Copy,
AudioBitrate = request.AudioBitrate,
AudioBufferSize = request.AudioBufferSize,

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

@ -73,7 +73,7 @@ public class UpdateFFmpegProfileHandler(IDbContextFactory<TvContext> dbContextFa @@ -73,7 +73,7 @@ public class UpdateFFmpegProfileHandler(IDbContextFactory<TvContext> dbContextFa
p.VideoBitrate = update.VideoBitrate;
p.VideoBufferSize = update.VideoBufferSize;
p.TonemapAlgorithm = update.TonemapAlgorithm;
p.AudioFormat = update.AudioFormat;
p.AudioFormat = update.NormalizeAudio ? update.AudioFormat : FFmpegProfileAudioFormat.Copy;
p.AudioBitrate = update.AudioBitrate;
p.AudioBufferSize = update.AudioBufferSize;

4
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -519,8 +519,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -519,8 +519,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
playbackSettings.VideoTrackTimeScale,
playbackSettings.Deinterlace);
// only use graphics engine when we have elements
if (graphicsElementContexts.Count > 0 || graphicsElements.Count > 0)
// only use graphics engine when we have elements, and are normalizing video
if (videoFormat != VideoFormat.Copy && (graphicsElementContexts.Count > 0 || graphicsElements.Count > 0))
{
FrameSize targetSize = await desiredState.CroppedSize.IfNoneAsync(desiredState.ScaledSize);

20
ErsatzTV.FFmpeg/OutputFormat/OutputFormatHls.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using ErsatzTV.FFmpeg.Environment;
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.OutputFormat;
@ -56,18 +57,27 @@ public class OutputFormatHls : IPipelineStep @@ -56,18 +57,27 @@ public class OutputFormatHls : IPipelineStep
? (int)Math.Round(frameRate.ParsedFrameRate)
: (int)Math.Round(frameRate.ParsedFrameRate * SegmentSeconds);
List<string> result =
List<string> result = [];
if (_desiredState.VideoFormat != VideoFormat.Copy)
{
result.AddRange(
[
"-g", $"{gop}",
"-keyint_min", $"{(int)Math.Round(frameRate.ParsedFrameRate * SegmentSeconds)}",
"-force_key_frames", $"expr:gte(t,n_forced*{SegmentSeconds})"
]);
}
result.AddRange(
[
"-g", $"{gop}",
"-keyint_min", $"{(int)Math.Round(frameRate.ParsedFrameRate * SegmentSeconds)}",
"-force_key_frames", $"expr:gte(t,n_forced*{SegmentSeconds})",
"-f", "hls",
"-hls_time", $"{SegmentSeconds}",
"-hls_list_size", "0",
"-segment_list_flags", "+live",
"-hls_segment_filename",
_segmentTemplate
];
]);
var independentSegments = "+independent_segments";

38
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -214,10 +214,14 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -214,10 +214,14 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
new LoglevelErrorOption(),
new StandardFormatFlags(),
new NoDemuxDecodeDelayOutputOption(),
outputOption,
new ClosedGopOutputOption()
outputOption
};
if (desiredState.VideoFormat != VideoFormat.Copy)
{
pipelineSteps.Add(new ClosedGopOutputOption());
}
if (desiredState.VideoFormat != VideoFormat.Copy && !desiredState.AllowBFrames)
{
pipelineSteps.Add(new NoBFramesOutputOption());
@ -512,6 +516,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -512,6 +516,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
private void SetAudioLoudness(AudioInputFile audioInputFile)
{
if (audioInputFile.DesiredState.AudioFormat == AudioFormat.Copy)
{
return;
}
if (audioInputFile.DesiredState.NormalizeLoudnessFilter is not AudioFilter.None)
{
var filter = new NormalizeLoudnessFilter(
@ -525,6 +534,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -525,6 +534,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
private static void SetAudioSampleRate(AudioInputFile audioInputFile, List<IPipelineStep> pipelineSteps)
{
if (audioInputFile.DesiredState.AudioFormat == AudioFormat.Copy)
{
return;
}
foreach (int desiredSampleRate in audioInputFile.DesiredState.AudioSampleRate)
{
pipelineSteps.Add(new AudioSampleRateOutputOption(desiredSampleRate));
@ -533,6 +547,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -533,6 +547,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
private static void SetAudioBufferSize(AudioInputFile audioInputFile, List<IPipelineStep> pipelineSteps)
{
if (audioInputFile.DesiredState.AudioFormat == AudioFormat.Copy)
{
return;
}
foreach (int desiredBufferSize in audioInputFile.DesiredState.AudioBufferSize)
{
pipelineSteps.Add(new AudioBufferSizeOutputOption(desiredBufferSize));
@ -541,6 +560,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -541,6 +560,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
private static void SetAudioBitrate(AudioInputFile audioInputFile, List<IPipelineStep> pipelineSteps)
{
if (audioInputFile.DesiredState.AudioFormat == AudioFormat.Copy)
{
return;
}
foreach (int desiredBitrate in audioInputFile.DesiredState.AudioBitrate)
{
pipelineSteps.Add(new AudioBitrateOutputOption(desiredBitrate));
@ -549,6 +573,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -549,6 +573,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
private static void SetAudioChannels(AudioInputFile audioInputFile, List<IPipelineStep> pipelineSteps)
{
if (audioInputFile.DesiredState.AudioFormat == AudioFormat.Copy)
{
return;
}
foreach (AudioStream audioStream in audioInputFile.AudioStreams.HeadOrNone())
{
foreach (int desiredAudioChannels in audioInputFile.DesiredState.AudioChannels)
@ -878,6 +907,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -878,6 +907,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
FrameState desiredState,
List<IPipelineStep> pipelineSteps)
{
if (desiredState.VideoFormat == VideoFormat.Copy)
{
return;
}
// -sc_threshold 0 is unsupported with mpeg2video
if (videoStream.Codec == VideoFormat.Mpeg2Video || desiredState.VideoFormat == VideoFormat.Mpeg2Video ||
ffmpegState.DecoderHardwareAccelerationMode == HardwareAccelerationMode.VideoToolbox)

30
ErsatzTV/Pages/Channels.razor

@ -98,11 +98,25 @@ @@ -98,11 +98,25 @@
<div style="align-items: center; display: flex;">
@if (_channelsThatCanPreview.Contains(context.Id))
{
<MudTooltip Text="Preview Channel">
<MudIconButton Icon="@Icons.Material.Filled.PlayCircle"
OnClick="@(_ => PreviewChannel(context))">
</MudIconButton>
</MudTooltip>
Option<FFmpegProfileViewModel> maybeProfile = Optional(_ffmpegProfiles.Find(p => p.Id == context.FFmpegProfileId));
bool notNormalizing = maybeProfile.Any(p => !p.NormalizeVideo || !p.NormalizeAudio);
if (notNormalizing)
{
<MudTooltip Text="Preview Channel; audio and/or video may not work without normalization">
<MudIconButton Icon="@Icons.Material.Filled.PlayCircle"
OnClick="@(_ => PreviewChannel(context))"
Style="color: var(--mud-palette-warning-lighten);">
</MudIconButton>
</MudTooltip>
}
else
{
<MudTooltip Text="Preview Channel">
<MudIconButton Icon="@Icons.Material.Filled.PlayCircle"
OnClick="@(_ => PreviewChannel(context))">
</MudIconButton>
</MudTooltip>
}
}
else if (CanPreviewChannel(context) && _ffmpegProfilesThatCanPreview.ContainsKey(context.FFmpegProfileId) && !_ffmpegProfilesThatCanPreview[context.FFmpegProfileId])
{
@ -272,6 +286,12 @@ @@ -272,6 +286,12 @@
//Console.WriteLine($"Checking video format {videoCodec} and audio format {audioCodec}");
// good luck
if (profile.VideoFormat == FFmpegProfileVideoFormat.Copy || profile.AudioFormat == FFmpegProfileAudioFormat.Copy)
{
return true;
}
if (string.IsNullOrWhiteSpace(videoCodec) || string.IsNullOrWhiteSpace(audioCodec))
{
return false;

33
ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

@ -49,7 +49,22 @@ public class FFmpegProfileEditViewModel @@ -49,7 +49,22 @@ public class FFmpegProfileEditViewModel
public int AudioBitrate { get; set; }
public int AudioBufferSize { get; set; }
public int AudioChannels { get; set; }
public FFmpegProfileAudioFormat AudioFormat { get; set; }
public FFmpegProfileAudioFormat AudioFormat
{
get
{
if (field == FFmpegProfileAudioFormat.Copy && NormalizeAudio)
{
return FFmpegProfileAudioFormat.Aac;
}
return field;
}
set;
}
public int AudioSampleRate { get; set; }
public NormalizeLoudnessMode NormalizeLoudnessMode
@ -105,7 +120,21 @@ public class FFmpegProfileEditViewModel @@ -105,7 +120,21 @@ public class FFmpegProfileEditViewModel
public int? QsvExtraHardwareFrames { get; set; }
public int VideoBitrate { get; set; }
public int VideoBufferSize { get; set; }
public FFmpegProfileVideoFormat VideoFormat { get; set; }
public FFmpegProfileVideoFormat VideoFormat
{
get
{
if (field == FFmpegProfileVideoFormat.Copy && NormalizeVideo)
{
return FFmpegProfileVideoFormat.H264;
}
return field;
}
set;
}
public string VideoProfile
{

Loading…
Cancel
Save