Files
jellyfin-plugin-enigma2/Tests/LosslessSlateTests.cs

64 lines
4.9 KiB
C#

using System;
using Jellyfin.Plugin.Enigma2;
using MediaBrowser.Model.Entities;
public static class LosslessSlateTests
{
public static BroadcastTracks Tracks(int width, int height, float rate) => new(new MediaStream[] {
new BroadcastMediaStream { Type=MediaStreamType.Video, Index=0, Codec="h264", Width=width, Height=height,
AverageFrameRate=rate, RealFrameRate=rate, Profile="Constrained Baseline", PixelFormat="yuv420p", SampleAspectRatio="1:1" },
new MediaStream { Type=MediaStreamType.Audio, Index=1, Codec="aac", Profile="LC", Channels=2, SampleRate=48000 }
});
public static void Run(Action<bool,string> check)
{
foreach (var rate in new[] { 24f, 25f, 30f, 50f, 60f, 120f })
{
var tracks=Tracks(3840,2160,rate);
var profile=LosslessSlateProfile.Create(tracks);
check(profile?.Rate==rate && profile.Width==3840 && profile.Height==2160 && tracks.Output[0].AverageFrameRate==rate,
$"status picture matches source rate {rate} and 4K size without changing channel metadata");
}
var original=Tracks(3840,2160,60000f/1001);
var video=(BroadcastMediaStream)original.Input[0];video.FrameRateRational="60000/1001";
check(LosslessSlateProfile.Create(original)==null && original.Output[0].AverageFrameRate==60000f/1001, "fractional cadence is preserved by native fallback instead of rounded for a status image");
var key=new BroadcastTracks(original.Input).Key;video.AverageFrameRate=24;
check(new BroadcastTracks(original.Input).Key!=key,"source rate changes invalidate cached status images");
video.Codec="hevc";video.PixelFormat="yuv420p10le";video.ColorTransfer="smpte2084";video.ColorPrimaries="bt2020";video.Level=153;
original.Input[1].Codec="ac3";original.Input[1].Channels=6;
var hdr=new BroadcastTracks(original.Input,true,false);
check(!hdr.Normalize && hdr.Output[0].Codec=="hevc" && hdr.Output[0].PixelFormat=="yuv420p10le" && hdr.Output[0].ColorTransfer=="smpte2084"
&& hdr.Output[0].Level==153 && hdr.Output[0].Width==3840 && hdr.Output[1].Codec=="ac3" && hdr.Output[1].Channels==6,
"legacy slate/Full HD options cannot normalize 4K HDR10 or surround audio");
check(LosslessSlateProfile.Create(hdr)==null,"HDR/HEVC keeps original transport instead of converting the channel to fit a status picture");
var interlaced=Tracks(1920,1080,25);interlaced.Input[0].IsInterlaced=true;
check(LosslessSlateProfile.Create(interlaced)==null && new BroadcastTracks(interlaced.Input).Output[0].IsInterlaced,
"interlaced broadcast is preserved without plugin deinterlacing");
check(LosslessSlateProfile.Create(BroadcastTracks.Fallback())==null,"unknown cold-start metadata cannot force a guessed status format");
var unknown=Tracks(1280,720,60);((BroadcastMediaStream)unknown.Input[0]).SampleAspectRatio=null;
check(LosslessSlateProfile.Create(unknown)==null,"unknown sample aspect ratio uses unmodified broadcast fallback");
var now=DateTime.UtcNow;var timeline=new ContinuityTimeline(now,false);
var first=Packet(12000,9000);var second=Packet(195000,189000);
timeline.Live(first,"producer",2,now);timeline.Live(second,"producer",2,now.AddSeconds(2));
long Decode(long sequence,byte[] packet)
{
var segment=timeline.Find(sequence);
return TransportStreamClock.ClockBounds(TransportStreamClock.Rebase(packet,segment.Start90k,false,segment.ClockOffset90k)).Value.Decode;
}
check(Decode(1,second)-Decode(0,first)==180000,"copied B-frames retain their decode-clock interval despite different first presentation timestamps");
timeline.Tick(now.AddSeconds(9),true);var retry=timeline.Find(2);
var recovered=Packet(372000,369000);timeline.Live(recovered,"producer",2,now.AddSeconds(10));
check(Decode(3,recovered)>=retry.Start90k+(long)(retry.Duration*90000),"short source stall followed by the same producer cannot rewind behind the inserted status picture");
timeline=new ContinuityTimeline(now,false);first=Packet((1L<<33)-6000,(1L<<33)-9000);second=Packet(93000,90000);
timeline.Live(first,"wrapped",1.1,now);timeline.Live(second,"wrapped",1.1,now.AddSeconds(1.1));
check(Decode(1,second)-Decode(0,first)==99000,"copied transport keeps a monotonic decode clock across the 33-bit timestamp wrap");
}
private static byte[] Packet(long pts,long dts)
{
var packet=new byte[188];packet[0]=71;packet[1]=65;packet[2]=0;packet[3]=16;
packet[6]=1;packet[7]=224;packet[10]=128;packet[11]=192;packet[12]=10;
void Stamp(int at,long time,int prefix)
{time&=(1L<<33)-1;packet[at]=(byte)((long)prefix|((time>>29)&14)|1);packet[at+1]=(byte)(time>>22);packet[at+2]=(byte)(1|((time>>14)&254));packet[at+3]=(byte)(time>>7);packet[at+4]=(byte)(1|(time<<1));}
Stamp(13,pts,48);Stamp(18,dts,16);return packet;
}
}