diff --git a/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs b/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs index fdf686d..0acf6e3 100644 --- a/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs +++ b/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs @@ -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(