| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 | 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;		}	}}
 |