Browse Source

add aac (latm) audio format (#2561)

* add aac (latm) audio format

* update changelog
pull/2562/head
Jason Dove 9 months ago committed by GitHub
parent
commit
a47510fef3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 1
      ErsatzTV.Core/Domain/FFmpegProfileAudioFormat.cs
  3. 51
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  4. 4
      ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs
  5. 24
      ErsatzTV.FFmpeg/Decoder/DecoderAacLatm.cs
  6. 2
      ErsatzTV.FFmpeg/FFmpegState.cs
  7. 1
      ErsatzTV.FFmpeg/Format/AudioFormat.cs
  8. 2
      ErsatzTV.FFmpeg/InputFile.cs
  9. 9
      ErsatzTV.FFmpeg/OutputFormat/OutputFormatHls.cs
  10. 12
      ErsatzTV.FFmpeg/OutputOption/AudioChannelsOutputOption.cs
  11. 7
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  12. 2
      ErsatzTV/Controllers/IptvController.cs
  13. 1
      ErsatzTV/Pages/FFmpegEditor.razor

1
CHANGELOG.md

@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- By default, poster will be added as image with type "poster" and thumbnail will be added as image with type "still"
- Poster will continue to be added as icon by default
- Add buttons to edit Jellyfin and Emby connection information in **Media Sources** > **Jellyfin** and **Media Sources** > **Emby**
- Add audio format `aac (latm)` for DVB-C compatibility; `aac` uses ADTS by default which is required in most cases
### Fixed
- Fix NVIDIA startup errors on arm64

1
ErsatzTV.Core/Domain/FFmpegProfileAudioFormat.cs

@ -9,6 +9,7 @@ public enum FFmpegProfileAudioFormat @@ -9,6 +9,7 @@ public enum FFmpegProfileAudioFormat
Aac = 1,
Ac3 = 2,
AacLatm = 3,
Copy = 99
}

51
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -171,6 +171,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -171,6 +171,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
string audioFormat = playbackSettings.AudioFormat switch
{
FFmpegProfileAudioFormat.Aac => AudioFormat.Aac,
FFmpegProfileAudioFormat.AacLatm => AudioFormat.AacLatm,
FFmpegProfileAudioFormat.Ac3 => AudioFormat.Ac3,
FFmpegProfileAudioFormat.Copy => AudioFormat.Copy,
_ => throw new ArgumentOutOfRangeException($"unexpected audio format {playbackSettings.VideoFormat}")
@ -403,6 +404,27 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -403,6 +404,27 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
_ => Option<string>.None
};
Option<string> hlsSegmentOptions = Option<string>.None;
if (outputFormat is OutputFormatKind.Hls)
{
string options = string.Empty;
if (ptsOffset == TimeSpan.Zero)
{
options += "+initial_discontinuity";
}
if (audioFormat == AudioFormat.AacLatm)
{
options += "+latm";
}
if (!string.IsNullOrWhiteSpace(options))
{
hlsSegmentOptions = $"mpegts_flags={options}";
}
}
FrameSize scaledSize = ffmpegVideoStream.SquarePixelFrameSize(
new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height));
@ -506,6 +528,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -506,6 +528,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
hlsPlaylistPath,
hlsSegmentTemplate,
hlsInitTemplate,
hlsSegmentOptions,
ptsOffset,
playbackSettings.ThreadCount,
qsvExtraHardwareFrames,
@ -583,6 +606,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -583,6 +606,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
string audioFormat = playbackSettings.AudioFormat switch
{
FFmpegProfileAudioFormat.Ac3 => AudioFormat.Ac3,
FFmpegProfileAudioFormat.AacLatm => AudioFormat.AacLatm,
_ => AudioFormat.Aac
};
@ -645,6 +669,27 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -645,6 +669,27 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
_ => Option<string>.None
};
Option<string> hlsSegmentOptions = Option<string>.None;
if (outputFormat is OutputFormatKind.Hls)
{
string options = string.Empty;
if (ptsOffset == TimeSpan.Zero)
{
options += "+initial_discontinuity";
}
if (audioFormat == AudioFormat.AacLatm)
{
options += "+latm";
}
if (!string.IsNullOrWhiteSpace(options))
{
hlsSegmentOptions = $"mpegts_options={options}";
}
}
string videoPath = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "background.png");
var videoVersion = BackgroundImageMediaVersion.ForPath(videoPath, desiredResolution);
@ -686,6 +731,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -686,6 +731,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
hlsPlaylistPath,
hlsSegmentTemplate,
hlsInitTemplate,
hlsSegmentOptions,
ptsOffset,
Option<int>.None,
qsvExtraHardwareFrames,
@ -778,6 +824,11 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -778,6 +824,11 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
$"http://localhost:{Settings.StreamingPort}/iptv/channel/{channel.Number}.m3u8?mode=segmenter{accessTokenQuery}",
resolution);
if (channel.FFmpegProfile.AudioFormat is FFmpegProfileAudioFormat.AacLatm)
{
concatInputFile.AudioFormat = AudioFormat.AacLatm;
}
IPipelineBuilder pipelineBuilder = await _pipelineBuilderFactory.GetBuilder(
HardwareAccelerationMode.None,
Option<VideoInputFile>.None,

4
ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs

@ -91,6 +91,7 @@ public class PipelineBuilderBaseTests @@ -91,6 +91,7 @@ public class PipelineBuilderBaseTests
Option<string>.None,
Option<string>.None,
Option<string>.None,
Option<string>.None,
TimeSpan.Zero,
Option<int>.None,
Option<int>.None,
@ -190,6 +191,7 @@ public class PipelineBuilderBaseTests @@ -190,6 +191,7 @@ public class PipelineBuilderBaseTests
Option<string>.None,
Option<string>.None,
Option<string>.None,
Option<string>.None,
TimeSpan.Zero,
Option<int>.None,
Option<int>.None,
@ -347,6 +349,7 @@ public class PipelineBuilderBaseTests @@ -347,6 +349,7 @@ public class PipelineBuilderBaseTests
Option<string>.None,
Option<string>.None,
Option<string>.None,
Option<string>.None,
TimeSpan.Zero,
Option<int>.None,
Option<int>.None,
@ -440,6 +443,7 @@ public class PipelineBuilderBaseTests @@ -440,6 +443,7 @@ public class PipelineBuilderBaseTests
Option<string>.None,
Option<string>.None,
Option<string>.None,
Option<string>.None,
TimeSpan.Zero,
Option<int>.None,
Option<int>.None,

24
ErsatzTV.FFmpeg/Decoder/DecoderAacLatm.cs

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
using ErsatzTV.FFmpeg.Environment;
using ErsatzTV.FFmpeg.InputOption;
namespace ErsatzTV.FFmpeg.Decoder;
public class DecoderAacLatm : IInputOption
{
public EnvironmentVariable[] EnvironmentVariables => [];
public string[] GlobalOptions => [];
public string[] FilterOptions => [];
public string[] OutputOptions => [];
public string[] InputOptions(InputFile inputFile) => ["-c:a", "aac_latm"];
public FrameState NextState(FrameState currentState) => currentState;
public bool AppliesTo(AudioInputFile audioInputFile) => false;
public bool AppliesTo(VideoInputFile videoInputFile) => false;
public bool AppliesTo(ConcatInputFile concatInputFile) => true;
public bool AppliesTo(GraphicsEngineInput graphicsEngineInput) => false;
}

2
ErsatzTV.FFmpeg/FFmpegState.cs

@ -20,6 +20,7 @@ public record FFmpegState( @@ -20,6 +20,7 @@ public record FFmpegState(
Option<string> HlsPlaylistPath,
Option<string> HlsSegmentTemplate,
Option<string> HlsInitTemplate,
Option<string> HlsSegmentOptions,
TimeSpan PtsOffset,
Option<int> ThreadCount,
Option<int> MaybeQsvExtraHardwareFrames,
@ -49,6 +50,7 @@ public record FFmpegState( @@ -49,6 +50,7 @@ public record FFmpegState(
Option<string>.None,
Option<string>.None,
Option<string>.None,
Option<string>.None,
TimeSpan.Zero,
Option<int>.None,
Option<int>.None,

1
ErsatzTV.FFmpeg/Format/AudioFormat.cs

@ -4,6 +4,7 @@ public static class AudioFormat @@ -4,6 +4,7 @@ public static class AudioFormat
{
public const string Aac = "aac";
public const string Ac3 = "ac3";
public const string AacLatm = "aac-latm";
public const string Copy = "copy";
}

2
ErsatzTV.FFmpeg/InputFile.cs

@ -28,6 +28,8 @@ public record ConcatInputFile(string Url, FrameSize Resolution) : InputFile( @@ -28,6 +28,8 @@ public record ConcatInputFile(string Url, FrameSize Resolution) : InputFile(
ScanKind.Unknown)
})
{
public Option<string> AudioFormat { get; set; }
public void AddOption(IInputOption option)
{
if (option.AppliesTo(this))

9
ErsatzTV.FFmpeg/OutputFormat/OutputFormatHls.cs

@ -11,6 +11,7 @@ public class OutputFormatHls : IPipelineStep @@ -11,6 +11,7 @@ public class OutputFormatHls : IPipelineStep
private readonly bool _isTroubleshooting;
private readonly Option<string> _mediaFrameRate;
private readonly OutputFormatKind _outputFormat;
private readonly Option<string> _segmentOptions;
private readonly bool _oneSecondGop;
private readonly string _playlistPath;
private readonly string _segmentTemplate;
@ -20,6 +21,7 @@ public class OutputFormatHls : IPipelineStep @@ -20,6 +21,7 @@ public class OutputFormatHls : IPipelineStep
FrameState desiredState,
Option<string> mediaFrameRate,
OutputFormatKind outputFormat,
Option<string> segmentOptions,
string segmentTemplate,
Option<string> initTemplate,
string playlistPath,
@ -30,6 +32,7 @@ public class OutputFormatHls : IPipelineStep @@ -30,6 +32,7 @@ public class OutputFormatHls : IPipelineStep
_desiredState = desiredState;
_mediaFrameRate = mediaFrameRate;
_outputFormat = outputFormat;
_segmentOptions = segmentOptions;
_segmentTemplate = segmentTemplate;
_initTemplate = initTemplate;
_playlistPath = playlistPath;
@ -83,6 +86,11 @@ public class OutputFormatHls : IPipelineStep @@ -83,6 +86,11 @@ public class OutputFormatHls : IPipelineStep
break;
}
foreach (string options in _segmentOptions)
{
result.AddRange("-hls_segment_options", options);
}
string pdt = _isTroubleshooting ? string.Empty : "program_date_time+omit_endlist+";
if (_isFirstTranscode)
@ -108,7 +116,6 @@ public class OutputFormatHls : IPipelineStep @@ -108,7 +116,6 @@ public class OutputFormatHls : IPipelineStep
result.AddRange(
[
"-hls_flags", $"{pdt}append_list+discont_start{independentSegments}",
"-mpegts_flags", "+initial_discontinuity",
_playlistPath
]);
break;

12
ErsatzTV.FFmpeg/OutputOption/AudioChannelsOutputOption.cs

@ -20,15 +20,17 @@ public class AudioChannelsOutputOption : OutputOption @@ -20,15 +20,17 @@ public class AudioChannelsOutputOption : OutputOption
{
get
{
if (_sourceChannels != _desiredChannels || _audioFormat == Some(AudioFormat.Aac) && _desiredChannels > 2)
if (_sourceChannels != _desiredChannels ||
(_audioFormat == Some(AudioFormat.Aac) || _audioFormat == Some(AudioFormat.AacLatm)) &&
_desiredChannels > 2)
{
return new[]
{
return
[
"-ac", _desiredChannels.ToString(CultureInfo.InvariantCulture)
};
];
}
return Array.Empty<string>();
return [];
}
}
}

7
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -162,6 +162,12 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -162,6 +162,12 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
new EncoderCopyAll()
};
// ffmpeg below 8 doesn't detect this without an explicit decoder
foreach (string _ in concatInputFile.AudioFormat.Where(af => af == AudioFormat.AacLatm))
{
concatInputFile.AddOption(new DecoderAacLatm());
}
concatInputFile.AddOption(new ReadrateInputOption(1.0));
SetMetadataServiceProvider(ffmpegState, pipelineSteps);
@ -384,6 +390,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -384,6 +390,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
desiredState,
videoStream.FrameRate,
ffmpegState.OutputFormat,
ffmpegState.HlsSegmentOptions,
segmentTemplate,
ffmpegState.HlsInitTemplate,
playlistPath,

2
ErsatzTV/Controllers/IptvController.cs

@ -307,7 +307,7 @@ public class IptvController : StreamingControllerBase @@ -307,7 +307,7 @@ public class IptvController : StreamingControllerBase
string audioCodec = streamingSpecs.AudioFormat switch
{
FFmpegProfileAudioFormat.Ac3 => "ac-3",
FFmpegProfileAudioFormat.Aac => "mp4a.40.2",
FFmpegProfileAudioFormat.Aac or FFmpegProfileAudioFormat.AacLatm => "mp4a.40.2",
_ => string.Empty
};

1
ErsatzTV/Pages/FFmpegEditor.razor

@ -242,6 +242,7 @@ @@ -242,6 +242,7 @@
<MudSelect @bind-Value="_model.AudioFormat" For="@(() => _model.AudioFormat)">
<MudSelectItem Value="@FFmpegProfileAudioFormat.Aac">aac</MudSelectItem>
<MudSelectItem Value="@FFmpegProfileAudioFormat.Ac3">ac3</MudSelectItem>
<MudSelectItem Value="@FFmpegProfileAudioFormat.AacLatm">aac (latm)</MudSelectItem>
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">

Loading…
Cancel
Save