[AvPlayer] improved video playback sync and stats

This commit is contained in:
ParantezTech 2026-09-29 15:00:41 +03:00
parent a9e1c25968
commit c4543a0ed1
1 changed files with 55 additions and 26 deletions

View File

@ -351,6 +351,8 @@ public static class AvPlayerExports
: VideoFrameReadResult.Pending;
}
public int QueuedFrameCount => _frames.Reader.Count;
private void DecodeFrames()
{
try
@ -456,6 +458,12 @@ public static class AvPlayerExports
public ulong LastVideoTimestamp { get; set; }
public ulong EventDataBuffer { get; set; }
public long NextFrameIndex { get; set; }
public long VideoCalls { get; set; }
public long VideoDelivered { get; set; }
public long VideoPending { get; set; }
public long VideoSyncWait { get; set; }
public long VideoWriteFailures { get; set; }
public long VideoStatsSecond { get; set; } = -1;
public ulong AudioBufferBase { get; set; }
public int NextAudioBuffer { get; set; }
public long NextAudioFrameIndex { get; set; }
@ -498,6 +506,12 @@ public static class AvPlayerExports
DisposePlaybackResources();
PlaybackClock.Reset();
NextFrameIndex = 0;
VideoCalls = 0;
VideoDelivered = 0;
VideoPending = 0;
VideoSyncWait = 0;
VideoWriteFailures = 0;
VideoStatsSecond = -1;
LastGuestBuffer = 0;
LastVideoTimestamp = 0;
SeekVideoFramePending = false;
@ -793,6 +807,7 @@ public static class AvPlayerExports
player.PlaybackClock.Stop();
player.FallbackPlayback?.Pause();
Console.Error.WriteLine($"[AVPLAYER][INFO] pause handle=0x{player.Handle:X16}");
TraceVideoStats(player);
}
@ -1619,6 +1634,14 @@ public static class AvPlayerExports
return SetReturn(ctx, 0);
}
player.VideoCalls++;
var statsSecond = player.PlaybackClock.ElapsedMilliseconds / 1000;
if (statsSecond > player.VideoStatsSecond)
{
player.VideoStatsSecond = statsSecond;
TraceVideoStats(player);
}
if (player.Paused)
{
if (player.SeekVideoFramePending &&
@ -1658,47 +1681,26 @@ public static class AvPlayerExports
}
var fps = Math.Max(1.0, player.FramesPerSecond);
var expectedFrame = player.NextFrameIndex;
if (player.AvSyncMode == AvSyncModeDefault)
{
var expectedFrame = CalculateExpectedDefaultSyncVideoFrame(
expectedFrame = CalculateExpectedDefaultSyncVideoFrame(
player.HasAudio,
player.NextAudioFrameIndex,
player.PlaybackClock.Elapsed.TotalSeconds,
fps);
if (player.NextFrameIndex > expectedFrame)
{
player.VideoSyncWait++;
return SetReturn(ctx, 0);
}
var behind = expectedFrame - player.NextFrameIndex;
if (behind > MaxCatchUpFrames)
{
// Limit this call's work without changing the shared playback clock.
expectedFrame = player.NextFrameIndex + MaxCatchUpFrames;
TraceOnce(
"catch_up_capped",
$"catch_up capped behind={behind} max={MaxCatchUpFrames} " +
$"fps={fps:F3} {player.Width}x{player.Height}");
}
while (player.NextFrameIndex < expectedFrame)
{
var skippedFrame = ReadFrame(player);
if (skippedFrame == VideoFrameReadResult.Pending)
{
return SetReturn(ctx, 0);
}
if (skippedFrame == VideoFrameReadResult.End)
{
return FinishStream(ctx, player);
}
player.NextFrameIndex++;
}
}
var frameResult = ReadFrame(player);
if (frameResult == VideoFrameReadResult.Pending)
{
player.VideoPending++;
return SetReturn(ctx, 0);
}
if (frameResult == VideoFrameReadResult.End)
@ -1707,9 +1709,23 @@ public static class AvPlayerExports
}
var frameIndex = player.NextFrameIndex;
// Only discard old frames when a replacement is already decoded.
for (var skipped = 0;
skipped < MaxCatchUpFrames &&
player.NextFrameIndex < expectedFrame &&
player.VideoDecoder!.QueuedFrameCount > 1;
skipped++)
{
player.NextFrameIndex++;
if (ReadFrame(player) != VideoFrameReadResult.Ready)
{
break;
}
frameIndex = player.NextFrameIndex;
}
var timestamp = player.StartTimeMilliseconds +
checked((ulong)Math.Round(frameIndex * 1000.0 / fps));
player.NextFrameIndex++;
player.NextFrameIndex = frameIndex + 1;
if (!WriteVideoFrame(
ctx,
player,
@ -1718,15 +1734,28 @@ public static class AvPlayerExports
frameIndex,
extended))
{
player.VideoWriteFailures++;
return SetReturn(ctx, 0);
}
player.LastVideoTimestamp = timestamp;
player.VideoDelivered++;
Trace($"video_frame handle=0x{player.Handle:X16} ex={extended} ts={timestamp} data=0x{player.LastGuestBuffer:X16}");
return SetReturn(ctx, 1);
}
}
private static void TraceVideoStats(PlayerState player)
{
Console.Error.WriteLine(
$"[AVPLAYER][INFO] video_stats handle=0x{player.Handle:X16} " +
$"clock_ms={player.PlaybackClock.ElapsedMilliseconds} audio_blocks={player.NextAudioFrameIndex} " +
$"calls={player.VideoCalls} delivered={player.VideoDelivered} " +
$"pending={player.VideoPending} sync_wait={player.VideoSyncWait} " +
$"write_fail={player.VideoWriteFailures} next_frame={player.NextFrameIndex} " +
$"queued={player.VideoDecoder?.QueuedFrameCount ?? 0} last_ts={player.LastVideoTimestamp}");
}
private static int FinishStream(CpuContext ctx, PlayerState player)
{
TraceOnce(