UP-Viagg-io/Viagg-io/Assets/Packages/AVProVideo/Runtime/Scripts/Internal/PlaybackQualityStats.cs

225 lines
6.6 KiB
C#
Raw Normal View History

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// Attempts to give insight into video playback presentation smoothness quality
/// Keeps track of skipped and duplicated frames and warns about suboptimal setup
/// such as no vsync enabled or video frame rate not being a multiple of the display frame rate
/// </summary>
public class PlaybackQualityStats
{
public int SkippedFrames { get; private set; }
public int DuplicateFrames { get; private set; }
public int UnityDroppedFrames { get; private set; }
public float PerfectFramesT { get; private set; }
public string VSyncStatus { get; private set; }
private int PerfectFrames { get; set; }
private int TotalFrames { get; set; }
public bool LogIssues { get; set; }
private int _sameFrameCount;
private long _lastTimeStamp;
private BaseMediaPlayer _player;
public void Reset()
{
_sameFrameCount = 0;
if (_player != null)
{
_lastTimeStamp = _player.GetTextureTimeStamp();
}
SkippedFrames = 0;
DuplicateFrames = 0;
UnityDroppedFrames = 0;
TotalFrames = 0;
PerfectFrames = 0;
PerfectFramesT = 0f;
}
internal void Start(BaseMediaPlayer player)
{
_player = player;
Reset();
bool vsyncEnabled = true;
if (QualitySettings.vSyncCount == 0)
{
vsyncEnabled = false;
if (LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] VSync is currently disabled in Quality Settings");
}
}
if (!IsGameViewVSyncEnabled())
{
vsyncEnabled = false;
if (LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] VSync is currently disabled in the Game View");
}
}
float frameRate = _player.GetVideoFrameRate();
float frameMs = (1000f / frameRate);
if (LogIssues)
{
Debug.Log(string.Format("[AVProVideo][Quality] Video: {0}fps {1}ms", frameRate, frameMs));
}
if (vsyncEnabled)
{
#if UNITY_2022_2_OR_NEWER
float refreshRate = (float)( Screen.currentResolution.refreshRateRatio.value );
#else
float refreshRate = (float)( Screen.currentResolution.refreshRate );
#endif
float vsyncRate = refreshRate / QualitySettings.vSyncCount;
float vsyncMs = (1000f / vsyncRate);
if (LogIssues)
{
Debug.Log(string.Format("[AVProVideo][Quality] VSync: {0}fps {1}ms", vsyncRate, vsyncMs));
}
float framesPerVSync = frameMs / vsyncMs;
float fractionalframesPerVsync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
if (fractionalframesPerVsync > 0.0001f && LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] Video is not a multiple of VSync so playback cannot be perfect");
}
VSyncStatus = "VSync " + framesPerVSync;
}
else
{
if (LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] Running without VSync enabled");
}
VSyncStatus = "No VSync";
}
}
internal void Update()
{
if (_player == null) return;
// Don't analyse stats unless real playback is happening
if (_player.IsPaused() || _player.IsSeeking() || _player.IsFinished()) return;
long timeStamp = _player.GetTextureTimeStamp();
long frameDuration = (long)(Helper.SecondsToHNS / _player.GetVideoFrameRate());
bool isPerfectFrame = true;
// Check for skipped frames
long d = (timeStamp - _lastTimeStamp);
if (d > 0)
{
const long threshold = 10000;
d -= frameDuration;
if (d > threshold)
{
int skippedFrames = Mathf.FloorToInt((float)d / (float)frameDuration);
if (LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] Possible frame skip, at " + timeStamp + " delta " + d + " = " + skippedFrames + " frames");
}
SkippedFrames += skippedFrames;
isPerfectFrame = false;
}
}
if (QualitySettings.vSyncCount != 0)
{
#if UNITY_2022_2_OR_NEWER
float refreshRate = (float)( Screen.currentResolution.refreshRateRatio.value );
#else
float refreshRate = (float)( Screen.currentResolution.refreshRate );
#endif
long vsyncDuration = (long)((QualitySettings.vSyncCount * Helper.SecondsToHNS) / refreshRate);
if (timeStamp != _lastTimeStamp)
{
float framesPerVSync = (float)frameDuration / (float)vsyncDuration;
//Debug.Log((float)frameDuration + " " + (float)vsyncDuration);
float fractionalFramesPerVSync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
//Debug.Log(framesPerVSync + " " + fractionalFramesPerVSync);
// VSync rate is a multiple of the video rate so we should be able to get perfectly smooth playback
if (fractionalFramesPerVSync <= 0.0001f)
{
// Check for duplicate frames
if (!Mathf.Approximately(_sameFrameCount, (int)framesPerVSync))
{
if (LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] Frame " + timeStamp + " was shown for " + _sameFrameCount + " frames instead of expected " + framesPerVSync);
}
DuplicateFrames++;
isPerfectFrame = false;
}
}
_sameFrameCount = 1;
}
else
{
// Count the number of Unity-frames the video-frame is displayed for
_sameFrameCount++;
}
// Check for Unity dropping frames
{
long frameTime = (long)(Time.deltaTime * Helper.SecondsToHNS);
if (frameTime > (vsyncDuration + (vsyncDuration / 3)))
{
if (LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] Possible Unity dropped frame, delta time: " + (Time.deltaTime * 1000f) + "ms");
}
UnityDroppedFrames++;
isPerfectFrame = false;
}
}
}
if (_lastTimeStamp != timeStamp)
{
if (isPerfectFrame)
{
PerfectFrames++;
}
TotalFrames++;
PerfectFramesT = (float)PerfectFrames / (float)TotalFrames;
}
_lastTimeStamp = timeStamp;
}
private static bool IsGameViewVSyncEnabled()
{
bool result = true;
#if UNITY_EDITOR && UNITY_2019_1_OR_NEWER
System.Reflection.Assembly assembly = typeof(UnityEditor.EditorWindow).Assembly;
System.Type type = assembly.GetType("UnityEditor.GameView");
UnityEditor.EditorWindow window = UnityEditor.EditorWindow.GetWindow(type);
System.Reflection.PropertyInfo prop = type.GetProperty("vSyncEnabled");
if (prop != null)
{
result = (bool)prop.GetValue(window);
}
#endif
return result;
}
}
}