PlaybackQualityStats.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. //-----------------------------------------------------------------------------
  5. // Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. namespace RenderHeads.Media.AVProVideo
  8. {
  9. /// <summary>
  10. /// Attempts to give insight into video playback presentation smoothness quality
  11. /// Keeps track of skipped and duplicated frames and warns about suboptimal setup
  12. /// such as no vsync enabled or video frame rate not being a multiple of the display frame rate
  13. /// </summary>
  14. public class PlaybackQualityStats
  15. {
  16. public int SkippedFrames { get; private set; }
  17. public int DuplicateFrames { get; private set; }
  18. public int UnityDroppedFrames { get; private set; }
  19. public float PerfectFramesT { get; private set; }
  20. public string VSyncStatus { get; private set; }
  21. private int PerfectFrames { get; set; }
  22. private int TotalFrames { get; set; }
  23. public bool LogIssues { get; set; }
  24. private int _sameFrameCount;
  25. private long _lastTimeStamp;
  26. private BaseMediaPlayer _player;
  27. public void Reset()
  28. {
  29. _sameFrameCount = 0;
  30. if (_player != null)
  31. {
  32. _lastTimeStamp = _player.GetTextureTimeStamp();
  33. }
  34. SkippedFrames = 0;
  35. DuplicateFrames = 0;
  36. UnityDroppedFrames = 0;
  37. TotalFrames = 0;
  38. PerfectFrames = 0;
  39. PerfectFramesT = 0f;
  40. }
  41. internal void Start(BaseMediaPlayer player)
  42. {
  43. _player = player;
  44. Reset();
  45. bool vsyncEnabled = true;
  46. if (QualitySettings.vSyncCount == 0)
  47. {
  48. vsyncEnabled = false;
  49. if (LogIssues)
  50. {
  51. Debug.LogWarning("[AVProVideo][Quality] VSync is currently disabled in Quality Settings");
  52. }
  53. }
  54. if (!IsGameViewVSyncEnabled())
  55. {
  56. vsyncEnabled = false;
  57. if (LogIssues)
  58. {
  59. Debug.LogWarning("[AVProVideo][Quality] VSync is currently disabled in the Game View");
  60. }
  61. }
  62. float frameRate = _player.GetVideoFrameRate();
  63. float frameMs = (1000f / frameRate);
  64. if (LogIssues)
  65. {
  66. Debug.Log(string.Format("[AVProVideo][Quality] Video: {0}fps {1}ms", frameRate, frameMs));
  67. }
  68. if (vsyncEnabled)
  69. {
  70. #if UNITY_2022_2_OR_NEWER
  71. float refreshRate = (float)( Screen.currentResolution.refreshRateRatio.value );
  72. #else
  73. float refreshRate = (float)( Screen.currentResolution.refreshRate );
  74. #endif
  75. float vsyncRate = refreshRate / QualitySettings.vSyncCount;
  76. float vsyncMs = (1000f / vsyncRate);
  77. if (LogIssues)
  78. {
  79. Debug.Log(string.Format("[AVProVideo][Quality] VSync: {0}fps {1}ms", vsyncRate, vsyncMs));
  80. }
  81. float framesPerVSync = frameMs / vsyncMs;
  82. float fractionalframesPerVsync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
  83. if (fractionalframesPerVsync > 0.0001f && LogIssues)
  84. {
  85. Debug.LogWarning("[AVProVideo][Quality] Video is not a multiple of VSync so playback cannot be perfect");
  86. }
  87. VSyncStatus = "VSync " + framesPerVSync;
  88. }
  89. else
  90. {
  91. if (LogIssues)
  92. {
  93. Debug.LogWarning("[AVProVideo][Quality] Running without VSync enabled");
  94. }
  95. VSyncStatus = "No VSync";
  96. }
  97. }
  98. internal void Update()
  99. {
  100. if (_player == null) return;
  101. // Don't analyse stats unless real playback is happening
  102. if (_player.IsPaused() || _player.IsSeeking() || _player.IsFinished()) return;
  103. long timeStamp = _player.GetTextureTimeStamp();
  104. long frameDuration = (long)(Helper.SecondsToHNS / _player.GetVideoFrameRate());
  105. bool isPerfectFrame = true;
  106. // Check for skipped frames
  107. long d = (timeStamp - _lastTimeStamp);
  108. if (d > 0)
  109. {
  110. const long threshold = 10000;
  111. d -= frameDuration;
  112. if (d > threshold)
  113. {
  114. int skippedFrames = Mathf.FloorToInt((float)d / (float)frameDuration);
  115. if (LogIssues)
  116. {
  117. Debug.LogWarning("[AVProVideo][Quality] Possible frame skip, at " + timeStamp + " delta " + d + " = " + skippedFrames + " frames");
  118. }
  119. SkippedFrames += skippedFrames;
  120. isPerfectFrame = false;
  121. }
  122. }
  123. if (QualitySettings.vSyncCount != 0)
  124. {
  125. #if UNITY_2022_2_OR_NEWER
  126. float refreshRate = (float)( Screen.currentResolution.refreshRateRatio.value );
  127. #else
  128. float refreshRate = (float)( Screen.currentResolution.refreshRate );
  129. #endif
  130. long vsyncDuration = (long)((QualitySettings.vSyncCount * Helper.SecondsToHNS) / refreshRate);
  131. if (timeStamp != _lastTimeStamp)
  132. {
  133. float framesPerVSync = (float)frameDuration / (float)vsyncDuration;
  134. //Debug.Log((float)frameDuration + " " + (float)vsyncDuration);
  135. float fractionalFramesPerVSync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
  136. //Debug.Log(framesPerVSync + " " + fractionalFramesPerVSync);
  137. // VSync rate is a multiple of the video rate so we should be able to get perfectly smooth playback
  138. if (fractionalFramesPerVSync <= 0.0001f)
  139. {
  140. // Check for duplicate frames
  141. if (!Mathf.Approximately(_sameFrameCount, (int)framesPerVSync))
  142. {
  143. if (LogIssues)
  144. {
  145. Debug.LogWarning("[AVProVideo][Quality] Frame " + timeStamp + " was shown for " + _sameFrameCount + " frames instead of expected " + framesPerVSync);
  146. }
  147. DuplicateFrames++;
  148. isPerfectFrame = false;
  149. }
  150. }
  151. _sameFrameCount = 1;
  152. }
  153. else
  154. {
  155. // Count the number of Unity-frames the video-frame is displayed for
  156. _sameFrameCount++;
  157. }
  158. // Check for Unity dropping frames
  159. {
  160. long frameTime = (long)(Time.deltaTime * Helper.SecondsToHNS);
  161. if (frameTime > (vsyncDuration + (vsyncDuration / 3)))
  162. {
  163. if (LogIssues)
  164. {
  165. Debug.LogWarning("[AVProVideo][Quality] Possible Unity dropped frame, delta time: " + (Time.deltaTime * 1000f) + "ms");
  166. }
  167. UnityDroppedFrames++;
  168. isPerfectFrame = false;
  169. }
  170. }
  171. }
  172. if (_lastTimeStamp != timeStamp)
  173. {
  174. if (isPerfectFrame)
  175. {
  176. PerfectFrames++;
  177. }
  178. TotalFrames++;
  179. PerfectFramesT = (float)PerfectFrames / (float)TotalFrames;
  180. }
  181. _lastTimeStamp = timeStamp;
  182. }
  183. private static bool IsGameViewVSyncEnabled()
  184. {
  185. bool result = true;
  186. #if UNITY_EDITOR && UNITY_2019_1_OR_NEWER
  187. System.Reflection.Assembly assembly = typeof(UnityEditor.EditorWindow).Assembly;
  188. System.Type type = assembly.GetType("UnityEditor.GameView");
  189. UnityEditor.EditorWindow window = UnityEditor.EditorWindow.GetWindow(type);
  190. System.Reflection.PropertyInfo prop = type.GetProperty("vSyncEnabled");
  191. if (prop != null)
  192. {
  193. result = (bool)prop.GetValue(window);
  194. }
  195. #endif
  196. return result;
  197. }
  198. }
  199. }