Preserve channel resolution through continuity slates up to UHD
This commit is contained in:
@@ -27,6 +27,7 @@ public sealed class ContinuityStreams : IDisposable
|
||||
private readonly string root;
|
||||
private Func<string> encoderPath;
|
||||
private readonly int port;
|
||||
private readonly bool preserveResolution;
|
||||
private readonly StartupMessages messages;
|
||||
private readonly ILogger logger;
|
||||
private readonly object gate = new();
|
||||
@@ -40,11 +41,11 @@ public sealed class ContinuityStreams : IDisposable
|
||||
private long attempts, liveSegments;
|
||||
public const int Width = 1920, Height = 1080, FrameRate = 50;
|
||||
public ContinuityStreams(IMediaEncoder encoder, IApplicationPaths paths, IServerApplicationHost host, StartupMessages messages, ILogger<ContinuityStreams> logger)
|
||||
: this(encoder.EncoderPath, Path.Combine(paths.CachePath, "enigma2-continuity-v1"), host.HttpPort, messages, logger) { encoderPath = () => encoder.EncoderPath; }
|
||||
: this(encoder.EncoderPath, Path.Combine(paths.CachePath, "enigma2-continuity-v1"), host.HttpPort, messages, logger, Plugin.Instance?.Configuration?.PreserveSourceResolution ?? true) { encoderPath = () => encoder.EncoderPath; }
|
||||
// Explicit inputs support an isolated FFmpeg/Kestrel test without a receiver or server configuration changes.
|
||||
public ContinuityStreams(string encoder, string root, int port, StartupMessages messages, ILogger logger)
|
||||
public ContinuityStreams(string encoder, string root, int port, StartupMessages messages, ILogger logger, bool preserveResolution = true)
|
||||
{
|
||||
encoderPath = () => encoder; this.root = Path.GetFullPath(root); this.port = port; this.messages = messages; this.logger = logger;
|
||||
this.preserveResolution = preserveResolution; encoderPath = () => encoder; this.root = Path.GetFullPath(root); this.port = port; this.messages = messages; this.logger = logger;
|
||||
Directory.CreateDirectory(this.root); sweeper = Task.Run(Sweep);
|
||||
}
|
||||
private sealed class Viewer
|
||||
@@ -76,8 +77,8 @@ public sealed class ContinuityStreams : IDisposable
|
||||
Protocol = MediaProtocol.Http, Container = "hls", IsInfiniteStream = true, SupportsProbing = false,
|
||||
SupportsDirectPlay = false, SupportsDirectStream = true, SupportsTranscoding = true, BufferMs = 1000,
|
||||
MediaStreams = new[] {
|
||||
new MediaStream { Type = MediaStreamType.Video, Index = -1, Codec = "h264", Profile = "Constrained Baseline", Level = 42,
|
||||
Width = Width, Height = Height, AverageFrameRate = FrameRate, RealFrameRate = FrameRate, IsInterlaced = false, BitRate = 12000000, PixelFormat = "yuv420p" },
|
||||
new MediaStream { Type = MediaStreamType.Video, Index = -1, Codec = "h264", Profile = "Constrained Baseline", Level = preserveResolution ? null : 42,
|
||||
Width = preserveResolution ? null : Width, Height = preserveResolution ? null : Height, AverageFrameRate = FrameRate, RealFrameRate = FrameRate, IsInterlaced = false, BitRate = preserveResolution ? null : 12000000, PixelFormat = "yuv420p" },
|
||||
new MediaStream { Type = MediaStreamType.Audio, Index = -1, Codec = "aac", Channels = 2, SampleRate = 48000, BitRate = 128000, IsDefault = true }
|
||||
}
|
||||
};
|
||||
@@ -149,9 +150,9 @@ public sealed class ContinuityStreams : IDisposable
|
||||
foreach (var source in sources.Values) source.Stop(); sources.Clear(); viewers.Clear();
|
||||
}
|
||||
}
|
||||
private static IEnumerable<string> CodecArguments() => new[] {
|
||||
private IEnumerable<string> CodecArguments(bool slate = false) => new[] {
|
||||
"-c:v", "libx264", "-threads", "2", "-preset", "ultrafast", "-tune", "zerolatency", "-crf", "20",
|
||||
"-maxrate", "12M", "-bufsize", "24M", "-profile:v", "baseline", "-level:v", "4.2", "-pix_fmt", "yuv420p",
|
||||
"-maxrate", preserveResolution && !slate ? "48M" : "12M", "-bufsize", preserveResolution && !slate ? "96M" : "24M", "-profile:v", "baseline", "-pix_fmt", "yuv420p",
|
||||
"-r", FrameRate.ToString(CultureInfo.InvariantCulture), "-g", "112", "-keyint_min", "112", "-sc_threshold", "0",
|
||||
"-c:a", "aac", "-ar", "48000", "-ac", "2", "-b:a", "128k" };
|
||||
private Process Start(IEnumerable<string> arguments, string folder)
|
||||
@@ -179,7 +180,7 @@ public sealed class ContinuityStreams : IDisposable
|
||||
var filter = $"drawbox=x=(iw-160)/2:y=260:w=160:h=100:color=0x58bfee:t=6,drawtext=fontfile={font}:text=TV:fontsize=42:fontcolor=0x58bfee:x=(w-tw)/2:y=285," +
|
||||
$"drawtext=fontfile={font}:textfile=message.txt:fontsize=42:fontcolor=white:line_spacing=22:x=(w-tw)/2:y=490";
|
||||
using var process = Start(new[] { "-f", "lavfi", "-i", $"color=c=0x101723:s={Width}x{Height}:r={FrameRate}", "-f", "lavfi", "-i", "anullsrc=r=48000:cl=stereo", "-vf", filter,
|
||||
"-map", "0:v:0", "-map", "1:a:0", "-t", "6.72" }.Concat(CodecArguments()).Concat(new[] { "-f", "hls", "-hls_time", "2.24", "-hls_list_size", "0", "-hls_segment_filename", "slate-%d.ts", "slate.m3u8" }), folder);
|
||||
"-map", "0:v:0", "-map", "1:a:0", "-t", "6.72" }.Concat(CodecArguments(slate: true)).Concat(new[] { "-f", "hls", "-hls_time", "2.24", "-hls_list_size", "0", "-hls_segment_filename", "slate-%d.ts", "slate.m3u8" }), folder);
|
||||
try { await process.WaitForExitAsync(lifetime.Token).WaitAsync(TimeSpan.FromSeconds(20), lifetime.Token); }
|
||||
finally { if (!process.HasExited) process.Kill(entireProcessTree: true); }
|
||||
if (process.ExitCode != 0) throw new IOException("FFmpeg slate generation failed");
|
||||
@@ -217,8 +218,8 @@ public sealed class ContinuityStreams : IDisposable
|
||||
Directory.CreateDirectory(folder);
|
||||
var args = new[] { "-rw_timeout", "10000000", "-analyzeduration", "5000000", "-probesize", "4000000", "-i", Url,
|
||||
"-map", "0:v:0", "-map", "0:a:0", "-sn", "-dn", "-vf",
|
||||
$"bwdif=mode=send_field:parity=auto:deint=interlaced,scale=w={Width}:h={Height}:force_original_aspect_ratio=decrease,pad={Width}:{Height}:(ow-iw)/2:(oh-ih)/2,setsar=1" }
|
||||
.Concat(CodecArguments()).Concat(new[] { "-f", "hls", "-hls_time", "2.24", "-hls_list_size", "6", "-hls_delete_threshold", "8", "-hls_flags", "delete_segments+temp_file+omit_endlist", "-hls_segment_filename", "segment-%09d.ts", "live.m3u8" });
|
||||
owner.preserveResolution ? "bwdif=mode=send_field:parity=auto:deint=interlaced" : $"bwdif=mode=send_field:parity=auto:deint=interlaced,scale=w={Width}:h={Height}:force_original_aspect_ratio=decrease,pad={Width}:{Height}:(ow-iw)/2:(oh-ih)/2,setsar=1" }
|
||||
.Concat(owner.CodecArguments()).Concat(new[] { "-f", "hls", "-hls_time", "2.24", "-hls_list_size", "6", "-hls_delete_threshold", "8", "-hls_flags", "delete_segments+temp_file+omit_endlist", "-hls_segment_filename", "segment-%09d.ts", "live.m3u8" });
|
||||
Interlocked.Increment(ref owner.attempts);
|
||||
using var process = owner.Start(args, folder);
|
||||
try
|
||||
@@ -237,7 +238,7 @@ public sealed class ContinuityStreams : IDisposable
|
||||
var match = Regex.Match(line, @"^segment-(\d{9})\.ts$");
|
||||
if (!match.Success || !int.TryParse(match.Groups[1].Value, out var index) || index <= highest) continue;
|
||||
var path = Path.Combine(folder, line);
|
||||
if (!File.Exists(path) || new FileInfo(path).Length > 8 * 1024 * 1024) continue;
|
||||
if (!File.Exists(path) || new FileInfo(path).Length > 32 * 1024 * 1024) continue;
|
||||
Timeline.Live(await File.ReadAllBytesAsync(path, lifetime.Token), epoch, duration, DateTime.UtcNow);
|
||||
highest = index; lastOutput = DateTime.UtcNow; delivered = true; Retrying = false; failures = 0; Interlocked.Increment(ref owner.liveSegments);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user