Browse Source

improve segmenter v2 hevc compliance with fmp4 (#1684)

pull/1685/head
Jason Dove 2 years ago committed by GitHub
parent
commit
19af303d76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 1
      ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs
  3. 49
      ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs
  4. 12
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  5. 47
      ErsatzTV.FFmpeg/OutputFormat/OutputFormatConcatHls.cs
  6. 4
      ErsatzTV/Startup.cs

2
CHANGELOG.md

@ -45,6 +45,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -45,6 +45,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed
- Use ffmpeg 7 in all docker images
- Show health checks at top of home page; scroll release notes if needed
- Improve `HLS Segmenter V2` compliance by serving fmp4 segments when `hevc` video format is selected
- > 1.5. The container format for HEVC video MUST be fMP4.
## [0.8.6-beta] - 2024-04-03
### Added

1
ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs

@ -107,7 +107,6 @@ public class StartFFmpegSessionHandler : IRequestHandler<StartFFmpegSession, Eit @@ -107,7 +107,6 @@ public class StartFFmpegSessionHandler : IRequestHandler<StartFFmpegSession, Eit
{
"segmenter-v2" => new HlsSessionWorkerV2(
_serviceScopeFactory,
_configElementRepository,
_localFileSystem,
_sessionWorkerV2Logger,
targetFramerate,

49
ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs

@ -5,11 +5,9 @@ using System.Timers; @@ -5,11 +5,9 @@ using System.Timers;
using CliWrap;
using CliWrap.Buffered;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Timer = System.Timers.Timer;
@ -21,7 +19,6 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker @@ -21,7 +19,6 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
private readonly SemaphoreSlim _slim = new(1, 1);
//private static int _workAheadCount;
private readonly IConfigElementRepository _configElementRepository;
private readonly string _host;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<HlsSessionWorkerV2> _logger;
@ -32,7 +29,6 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker @@ -32,7 +29,6 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
private CancellationTokenSource _cancellationTokenSource;
private string _channelNumber;
private bool _disposedValue;
private bool _hasWrittenSegments;
private DateTimeOffset _lastAccess;
private Option<PlayoutItemProcessModel> _lastProcessModel;
private IServiceScope _serviceScope;
@ -42,7 +38,6 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker @@ -42,7 +38,6 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
public HlsSessionWorkerV2(
IServiceScopeFactory serviceScopeFactory,
IConfigElementRepository configElementRepository,
ILocalFileSystem localFileSystem,
ILogger<HlsSessionWorkerV2> logger,
Option<int> targetFramerate,
@ -51,7 +46,6 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker @@ -51,7 +46,6 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
{
_serviceScope = serviceScopeFactory.CreateScope();
_mediator = _serviceScope.ServiceProvider.GetRequiredService<IMediator>();
_configElementRepository = configElementRepository;
_localFileSystem = localFileSystem;
_logger = logger;
_targetFramerate = targetFramerate;
@ -298,8 +292,6 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker @@ -298,8 +292,6 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
foreach (PlayoutItemProcessModel processModel in result.RightToSeq())
{
_hasWrittenSegments = true;
_logger.LogDebug("Next playout item process will transcode until {Until}", processModel.Until);
_transcodedUntil = processModel.Until;
@ -366,45 +358,4 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker @@ -366,45 +358,4 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
return result;
}
private async Task<long> GetPtsOffset(string channelNumber, CancellationToken cancellationToken)
{
await _slim.WaitAsync(cancellationToken);
try
{
long result = 0;
// if we haven't yet written any segments, start at zero
if (!_hasWrittenSegments)
{
return result;
}
Either<BaseError, PtsAndDuration> queryResult = await _mediator.Send(
new GetLastPtsDuration(channelNumber),
cancellationToken);
foreach (BaseError error in queryResult.LeftToSeq())
{
_logger.LogWarning("Unable to determine last pts offset - {Error}", error.ToString());
}
foreach ((long pts, long duration) in queryResult.RightToSeq())
{
result = pts + duration + 1;
}
return result;
}
finally
{
_slim.Release();
}
}
private async Task<int> GetWorkAheadLimit() =>
await _configElementRepository.GetValue<int>(ConfigElementKey.FFmpegWorkAheadSegmenters)
.Map(maybeCount => maybeCount.Match(identity, () => 1));
private sealed record Segment(string File, int SequenceNumber);
}

12
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -651,10 +651,14 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -651,10 +651,14 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
Option<string> hlsPlaylistPath = Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live.m3u8");
Option<string> hlsSegmentTemplate = Path.Combine(
FileSystemLayout.TranscodeFolder,
channel.Number,
"live%06d.ts");
Option<string> hlsSegmentTemplate = videoFormat switch
{
// hls/hevc needs mp4
VideoFormat.Hevc => Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live%06d.m4s"),
// hls is otherwise fine with ts
_ => Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live%06d.ts")
};
var desiredState = new FrameState(
playbackSettings.RealtimeOutput,

47
ErsatzTV.FFmpeg/OutputFormat/OutputFormatConcatHls.cs

@ -18,21 +18,38 @@ public class OutputFormatConcatHls : IPipelineStep @@ -18,21 +18,38 @@ public class OutputFormatConcatHls : IPipelineStep
public string[] InputOptions(InputFile inputFile) => Array.Empty<string>();
public string[] FilterOptions => Array.Empty<string>();
public string[] OutputOptions =>
[
//"-g", $"{gop}",
//"-keyint_min", $"{FRAME_RATE * OutputFormatHls.SegmentSeconds}",
"-force_key_frames", $"expr:gte(t,n_forced*{OutputFormatHls.SegmentSeconds}/2)",
"-f", "hls",
//"-hls_segment_type", "fmp4",
//"-hls_init_time", "2",
"-hls_time", $"{OutputFormatHls.SegmentSeconds}",
"-hls_list_size", "25", // burst of 45 means ~12 segments, so allow that plus a handful
"-segment_list_flags", "+live",
"-hls_segment_filename", _segmentTemplate,
"-hls_flags", "delete_segments+program_date_time+omit_endlist+discont_start+independent_segments",
_playlistPath
];
public string[] OutputOptions
{
get
{
string segmentType = "mpegts";
string hlsFlags = "delete_segments+program_date_time+omit_endlist+discont_start+independent_segments";
// check for fmp4 output
if (_segmentTemplate.Contains("m4s"))
{
segmentType = "fmp4";
hlsFlags = "delete_segments+program_date_time+omit_endlist";
}
return
[
//"-g", $"{gop}",
//"-keyint_min", $"{FRAME_RATE * OutputFormatHls.SegmentSeconds}",
"-force_key_frames", $"expr:gte(t,n_forced*{OutputFormatHls.SegmentSeconds}/2)",
"-f", "hls",
"-hls_segment_type", segmentType,
//"-hls_init_time", "2",
"-hls_playlist_type", "event",
"-hls_time", $"{OutputFormatHls.SegmentSeconds}",
"-hls_list_size", "25", // burst of 45 means ~12 segments, so allow that plus a handful
"-segment_list_flags", "+live",
"-hls_segment_filename", _segmentTemplate,
"-hls_flags", hlsFlags,
_playlistPath
];
}
}
public FrameState NextState(FrameState currentState) => currentState;
}

4
ErsatzTV/Startup.cs

@ -529,9 +529,9 @@ public class Startup @@ -529,9 +529,9 @@ public class Startup
ChannelWriter<IFFmpegWorkerRequest> writer = app.ApplicationServices
.GetRequiredService<ChannelWriter<IFFmpegWorkerRequest>>();
writer.TryWrite(new TouchFFmpegSession(ctx.File.PhysicalPath));
}
},
// to serve m4s
// ServeUnknownFileTypes = true
ServeUnknownFileTypes = true
});
app.MapWhen(

Loading…
Cancel
Save