WindowsMediaPlayer_BufferedFrames.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. // NOTE: We only allow this script to compile in editor so we can easily check for compilation issues
  2. #if (UNITY_EDITOR || (UNITY_STANDALONE_WIN || UNITY_WSA_10_0))
  3. #define AVPROVIDEO_FIXREGRESSION_TEXTUREQUALITY_UNITY542
  4. #if UNITY_WSA_10 || ENABLE_IL2CPP
  5. #define AVPROVIDEO_MARSHAL_RETURN_BOOL
  6. #endif
  7. #if UNITY_2019_3_OR_NEWER && !UNITY_2020_1_OR_NEWER
  8. #define AVPROVIDEO_FIX_UPDATEEXTERNALTEXTURE_LEAK
  9. #endif
  10. using UnityEngine;
  11. using System.Runtime.InteropServices;
  12. using System.Collections.Generic;
  13. using System;
  14. using System.Text;
  15. #if NETFX_CORE
  16. using Windows.Storage.Streams;
  17. #endif
  18. //-----------------------------------------------------------------------------
  19. // Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
  20. //-----------------------------------------------------------------------------
  21. namespace RenderHeads.Media.AVProVideo
  22. {
  23. /// <summary>
  24. /// Windows desktop and UWP implementation of BaseMediaPlayer
  25. /// </summary>
  26. public /*sealed*/ partial class WindowsMediaPlayer : BaseMediaPlayer
  27. {
  28. #region IBufferedDisplay Implementation
  29. private BufferedFrameSelectionMode _frameSelectionMode = BufferedFrameSelectionMode.None;
  30. private bool _pauseOnPrerollComplete = false;
  31. private IBufferedDisplay _masterDisplay;
  32. private IBufferedDisplay[] _slaveDisplays;
  33. private double _displayClockTime = 0.0;
  34. private double _timeAccumulation = 0.0;
  35. private bool _needsInitialFrame = true;
  36. private void FlushFrameBuffering(bool releaseTexture)
  37. {
  38. if (_frameSelectionMode == BufferedFrameSelectionMode.None) return;
  39. if (releaseTexture && _textureFrame.internalNativePointer != System.IntPtr.Zero)
  40. {
  41. Native.UnlockTextureFrame(_instance, ref _textureFrame);
  42. _textureFrame.internalNativePointer = System.IntPtr.Zero;
  43. _textureFrame.texturePointer = System.IntPtr.Zero;
  44. }
  45. Native.FlushFrameBuffering(_instance);
  46. // Native _pauseOnPrerollComplete needs to be reset
  47. //Native.SetFrameBufferingEnabled(_instance, (_frameSelectionMode != BufferedFrameSelectionMode.None), _pauseOnPrerollComplete);
  48. _needsInitialFrame = true;
  49. _timeAccumulation = 0.0;
  50. }
  51. internal override long InternalUpdateBufferedDisplay()
  52. {
  53. BufferedFramesState state = GetBufferedFramesState();
  54. if (state.bufferedFrameCount > 0)
  55. {
  56. if (_frameSelectionMode == BufferedFrameSelectionMode.NewestFrame)
  57. {
  58. SetBufferedDisplayTime(_frameSelectionMode, -1, false);
  59. }
  60. else if (_frameSelectionMode == BufferedFrameSelectionMode.OldestFrame)
  61. {
  62. SetBufferedDisplayTime(_frameSelectionMode, -1, false);
  63. }
  64. else if (_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTime ||
  65. _frameSelectionMode == BufferedFrameSelectionMode.ElapsedTimeVsynced)
  66. {
  67. // Only start consuming frames on these conditions
  68. bool needsInitialFrame = (_textureFrame.texturePointer == System.IntPtr.Zero || _needsInitialFrame);
  69. bool playingBufferedFrames = (IsPrerollComplete() && (IsPlaying() || (!IsPlaying() && IsFinished())));
  70. if (needsInitialFrame || playingBufferedFrames)
  71. {
  72. if (needsInitialFrame)
  73. {
  74. if (SetBufferedDisplayTime(BufferedFrameSelectionMode.OldestFrame, -1, true))
  75. {
  76. _displayClockTime = _textureFrame.timeStamp;
  77. _needsInitialFrame = false;
  78. }
  79. }
  80. else
  81. {
  82. // TODO: run without vsync, just show next frame (use media clock for present?)
  83. // use our own clock...
  84. const double SecondsToHNS = 10000000.0;
  85. double videoFrameDuration = SecondsToHNS / (double)GetVideoFrameRate();
  86. long videoDuration = (long)Math.Floor(SecondsToHNS * GetDuration());
  87. long lastFrameTime = Math.Max(videoDuration, state.maxTimeStamp);
  88. double delta = SecondsToHNS * Time.deltaTime;
  89. if (_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTimeVsynced && QualitySettings.vSyncCount > 0)
  90. {
  91. // Since we're running with vsync enabled, the MINIMUM elapsed time will be 1 monitor refresh (multiplied by QualitySettings.vSyncCount)
  92. double monitorDuration = (QualitySettings.vSyncCount * SecondsToHNS) / (double)Screen.currentResolution.refreshRate;
  93. int wholeFrames = (int)System.Math.Floor(_timeAccumulation / monitorDuration);
  94. wholeFrames = System.Math.Max(1, wholeFrames);
  95. delta = monitorDuration * wholeFrames;
  96. //LogBufferState();
  97. if (wholeFrames > 1)
  98. {
  99. //Debug.Log(Time.frameCount + "] " + Time.deltaTime + " " + wholeFrames + " " + _timeAccumulation + " " + _timeAccumulation / SecondsToHNS);
  100. //LogBufferState();
  101. }
  102. _timeAccumulation += (Time.deltaTime * SecondsToHNS) - delta;
  103. //delta = monitorDuration;
  104. /*double actualFrameDuration = Time.deltaTime * SecondsToHNS;
  105. double idealFrameTimeDifference = (actualFrameDuration);// - minMonitorDuration);
  106. if (idealFrameTimeDifference > (minMonitorDuration / 2))
  107. {
  108. int droppedFrames = (int)Math.Round(idealFrameTimeDifference / minMonitorDuration);
  109. //Debug.Log(Time.maximumDeltaTime + " " + Time.deltaTime + " " + actualFrameDuration + " " + idealFrameTimeDifference + " = " + droppedFrames);
  110. delta += minMonitorDuration * droppedFrames;
  111. //LogBufferState();
  112. }
  113. else
  114. {
  115. //Debug.Log(Time.deltaTime);
  116. }
  117. // If we're running slower than this or there is a frame drop, the elapsed time will be a multiple
  118. // of the monitor refresh rate
  119. */
  120. }
  121. _displayClockTime += delta;
  122. int multiple = (int)videoFrameDuration;
  123. long snappedFrameTime = (long)Math.Floor(_displayClockTime / multiple) * multiple;
  124. if (_isLooping && snappedFrameTime > lastFrameTime)
  125. {
  126. snappedFrameTime %= lastFrameTime;
  127. _needsInitialFrame = true;
  128. }
  129. else
  130. {
  131. snappedFrameTime = Math.Min(snappedFrameTime, lastFrameTime);
  132. }
  133. if (System.Math.Abs(snappedFrameTime - _textureFrame.timeStamp) > 1000)
  134. {
  135. //Debug.Log("1 " + _displayClockTime + " > " + snappedFrameTime + " d:" + delta);
  136. //LogBufferState();
  137. if (_needsInitialFrame)
  138. {
  139. if (SetBufferedDisplayTime(BufferedFrameSelectionMode.OldestFrame, -1, true))
  140. {
  141. _displayClockTime = _textureFrame.timeStamp;
  142. //Debug.Log("initial: " + _displayClockTime);
  143. _needsInitialFrame = false;
  144. }
  145. }
  146. else if (!SetBufferedDisplayTime(_frameSelectionMode, snappedFrameTime, false))
  147. {
  148. //Debug.LogWarning("[AVProVideo] failed to set time at " + snappedFrameTime);
  149. //LogBufferState();
  150. // Try to snap to oldest buffered time
  151. _displayClockTime = (state.minTimeStamp + state.maxTimeStamp) / 2.0;
  152. snappedFrameTime = (long)Math.Floor(_displayClockTime / multiple) * multiple;
  153. if (_isLooping && snappedFrameTime > lastFrameTime)
  154. {
  155. snappedFrameTime %= lastFrameTime;
  156. }
  157. else
  158. {
  159. snappedFrameTime = Math.Min(snappedFrameTime, lastFrameTime);
  160. }
  161. if (SetBufferedDisplayTime(BufferedFrameSelectionMode.FromExternalTimeClosest, snappedFrameTime, false))
  162. {
  163. _displayClockTime = _textureFrame.timeStamp;
  164. //Debug.LogWarning("[AVProVideo] Good set: " + _displayClockTime);
  165. }
  166. else
  167. {
  168. //Debug.LogWarning("[AVProVideo] Failed to display frame time " + snappedFrameTime);
  169. //LogBufferState();
  170. }
  171. }
  172. }
  173. }
  174. }
  175. }
  176. else if (_frameSelectionMode == BufferedFrameSelectionMode.FromExternalTime)
  177. {
  178. if (_masterDisplay != null)
  179. {
  180. // Use the time from the master
  181. long timeStamp = _masterDisplay.UpdateBufferedDisplay();
  182. if (timeStamp != GetTextureTimeStamp())
  183. {
  184. if (!SetBufferedDisplayTime(BufferedFrameSelectionMode.FromExternalTimeClosest, timeStamp, false))
  185. {
  186. Debug.LogWarning("[AVProVideo] Failed to display frame using external clock at time " + timeStamp);
  187. }
  188. }
  189. }
  190. }
  191. }
  192. return GetTextureTimeStamp();
  193. }
  194. private void LogBufferState()
  195. {
  196. BufferedFramesState state = GetBufferedFramesState();
  197. long timeStamp = GetTextureTimeStamp();
  198. string result = string.Format("[AVProVideo] {4} - {2},{3}\t\t{0}-{1} ({5})", state.minTimeStamp, state.maxTimeStamp, state.bufferedFrameCount, state.freeFrameCount, timeStamp, Time.deltaTime);
  199. Debug.Log(result);
  200. }
  201. private bool SetBufferedDisplayTime(BufferedFrameSelectionMode mode, long timeOfDesiredFrameToDisplay, bool ignorePreroll)
  202. {
  203. bool result = false;
  204. //if (!_isPaused)
  205. {
  206. result = Native.LockTextureFrame(_instance, mode, timeOfDesiredFrameToDisplay, ref _textureFrame, ignorePreroll);
  207. }
  208. return result;
  209. }
  210. public override BufferedFramesState GetBufferedFramesState()
  211. {
  212. BufferedFramesState state = new BufferedFramesState();
  213. Native.GetBufferedFramesState(_instance, ref state);
  214. return state;
  215. }
  216. public override void SetBufferedDisplayMode(BufferedFrameSelectionMode mode, IBufferedDisplay master = null)
  217. {
  218. _frameSelectionMode = mode;
  219. _masterDisplay = master;
  220. UpdateBufferedDisplay();
  221. }
  222. public override void SetBufferedDisplayOptions(bool pauseOnPrerollComplete)
  223. {
  224. _pauseOnPrerollComplete = pauseOnPrerollComplete;
  225. Native.SetFrameBufferingEnabled(_instance, (_frameSelectionMode != BufferedFrameSelectionMode.None), _pauseOnPrerollComplete);
  226. }
  227. public override void SetSlaves(IBufferedDisplay[] slaves)
  228. {
  229. foreach (IBufferedDisplay slave in slaves)
  230. {
  231. slave.SetBufferedDisplayMode(BufferedFrameSelectionMode.FromExternalTime, this);
  232. }
  233. _slaveDisplays = slaves;
  234. }
  235. private bool IsPrerollComplete()
  236. {
  237. bool result = true;
  238. if (GetBufferedFramesState().prerolledCount <= 0)
  239. {
  240. result = false;
  241. }
  242. if (_slaveDisplays != null && result)
  243. {
  244. foreach (IBufferedDisplay slave in _slaveDisplays)
  245. {
  246. if (slave.GetBufferedFramesState().prerolledCount <= 0)
  247. {
  248. result = false;
  249. break;
  250. }
  251. }
  252. }
  253. return result;
  254. }
  255. private partial struct Native
  256. {
  257. [DllImport("AVProVideo")]
  258. public static extern bool GetBufferedFramesState(System.IntPtr playerInstance, ref BufferedFramesState state);
  259. [DllImport("AVProVideo")]
  260. #if AVPROVIDEO_MARSHAL_RETURN_BOOL
  261. [return: MarshalAs(UnmanagedType.I1)]
  262. #endif
  263. public static extern bool LockTextureFrame(System.IntPtr instance, BufferedFrameSelectionMode mode, long time, ref TextureFrame textureFrame, bool ignorePreroll);
  264. [DllImport("AVProVideo")]
  265. public static extern void UnlockTextureFrame(System.IntPtr instance, ref TextureFrame textureFrame);
  266. [DllImport("AVProVideo")]
  267. public static extern void ReleaseTextureFrame(System.IntPtr instance, ref TextureFrame textureFrame);
  268. [DllImport("AVProVideo")]
  269. public static extern void FlushFrameBuffering(System.IntPtr instance);
  270. }
  271. #endregion // IBufferedDisplay Implementation
  272. }
  273. }
  274. #endif