PlaybackQualityStats.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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. float vsyncRate = (float)Screen.currentResolution.refreshRate / QualitySettings.vSyncCount;
  71. float vsyncMs = (1000f / vsyncRate);
  72. if (LogIssues)
  73. {
  74. Debug.Log(string.Format("[AVProVideo][Quality] VSync: {0}fps {1}ms", vsyncRate, vsyncMs));
  75. }
  76. float framesPerVSync = frameMs / vsyncMs;
  77. float fractionalframesPerVsync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
  78. if (fractionalframesPerVsync > 0.0001f && LogIssues)
  79. {
  80. Debug.LogWarning("[AVProVideo][Quality] Video is not a multiple of VSync so playback cannot be perfect");
  81. }
  82. VSyncStatus = "VSync " + framesPerVSync;
  83. }
  84. else
  85. {
  86. if (LogIssues)
  87. {
  88. Debug.LogWarning("[AVProVideo][Quality] Running without VSync enabled");
  89. }
  90. VSyncStatus = "No VSync";
  91. }
  92. }
  93. internal void Update()
  94. {
  95. if (_player == null) return;
  96. // Don't analyse stats unless real playback is happening
  97. if (_player.IsPaused() || _player.IsSeeking() || _player.IsFinished()) return;
  98. long timeStamp = _player.GetTextureTimeStamp();
  99. long frameDuration = (long)(Helper.SecondsToHNS / _player.GetVideoFrameRate());
  100. bool isPerfectFrame = true;
  101. // Check for skipped frames
  102. long d = (timeStamp - _lastTimeStamp);
  103. if (d > 0)
  104. {
  105. const long threshold = 10000;
  106. d -= frameDuration;
  107. if (d > threshold)
  108. {
  109. int skippedFrames = Mathf.FloorToInt((float)d / (float)frameDuration);
  110. if (LogIssues)
  111. {
  112. Debug.LogWarning("[AVProVideo][Quality] Possible frame skip, at " + timeStamp + " delta " + d + " = " + skippedFrames + " frames");
  113. }
  114. SkippedFrames += skippedFrames;
  115. isPerfectFrame = false;
  116. }
  117. }
  118. if (QualitySettings.vSyncCount != 0)
  119. {
  120. long vsyncDuration = (long)((QualitySettings.vSyncCount * Helper.SecondsToHNS) / (float)Screen.currentResolution.refreshRate);
  121. if (timeStamp != _lastTimeStamp)
  122. {
  123. float framesPerVSync = (float)frameDuration / (float)vsyncDuration;
  124. //Debug.Log((float)frameDuration + " " + (float)vsyncDuration);
  125. float fractionalFramesPerVSync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
  126. //Debug.Log(framesPerVSync + " " + fractionalFramesPerVSync);
  127. // VSync rate is a multiple of the video rate so we should be able to get perfectly smooth playback
  128. if (fractionalFramesPerVSync <= 0.0001f)
  129. {
  130. // Check for duplicate frames
  131. if (!Mathf.Approximately(_sameFrameCount, (int)framesPerVSync))
  132. {
  133. if (LogIssues)
  134. {
  135. Debug.LogWarning("[AVProVideo][Quality] Frame " + timeStamp + " was shown for " + _sameFrameCount + " frames instead of expected " + framesPerVSync);
  136. }
  137. DuplicateFrames++;
  138. isPerfectFrame = false;
  139. }
  140. }
  141. _sameFrameCount = 1;
  142. }
  143. else
  144. {
  145. // Count the number of Unity-frames the video-frame is displayed for
  146. _sameFrameCount++;
  147. }
  148. // Check for Unity dropping frames
  149. {
  150. long frameTime = (long)(Time.deltaTime * Helper.SecondsToHNS);
  151. if (frameTime > (vsyncDuration + (vsyncDuration / 3)))
  152. {
  153. if (LogIssues)
  154. {
  155. Debug.LogWarning("[AVProVideo][Quality] Possible Unity dropped frame, delta time: " + (Time.deltaTime * 1000f) + "ms");
  156. }
  157. UnityDroppedFrames++;
  158. isPerfectFrame = false;
  159. }
  160. }
  161. }
  162. if (_lastTimeStamp != timeStamp)
  163. {
  164. if (isPerfectFrame)
  165. {
  166. PerfectFrames++;
  167. }
  168. TotalFrames++;
  169. PerfectFramesT = (float)PerfectFrames / (float)TotalFrames;
  170. }
  171. _lastTimeStamp = timeStamp;
  172. }
  173. private static bool IsGameViewVSyncEnabled()
  174. {
  175. bool result = true;
  176. #if UNITY_EDITOR && UNITY_2019_1_OR_NEWER
  177. System.Reflection.Assembly assembly = typeof(UnityEditor.EditorWindow).Assembly;
  178. System.Type type = assembly.GetType("UnityEditor.GameView");
  179. UnityEditor.EditorWindow window = UnityEditor.EditorWindow.GetWindow(type);
  180. System.Reflection.PropertyInfo prop = type.GetProperty("vSyncEnabled");
  181. if (prop != null)
  182. {
  183. result = (bool)prop.GetValue(window);
  184. }
  185. #endif
  186. return result;
  187. }
  188. }
  189. }