Resampler.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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. /// Utility class to resample MediaPlayer video frames to allow for smoother playback
  11. /// Keeps a buffer of frames with timestamps and presents them using its own clock
  12. /// </summary>
  13. public class Resampler
  14. {
  15. private class TimestampedRenderTexture
  16. {
  17. public RenderTexture texture = null;
  18. public long timestamp = 0;
  19. public bool used = false;
  20. }
  21. public enum ResampleMode
  22. {
  23. POINT, LINEAR
  24. }
  25. private List<TimestampedRenderTexture[]> _buffer = new List<TimestampedRenderTexture[]>();
  26. private MediaPlayer _mediaPlayer;
  27. private RenderTexture[] _outputTexture = null;
  28. private int _start = 0;
  29. private int _end = 0;
  30. private int _bufferSize = 0;
  31. private long _baseTimestamp = 0;
  32. private float _elapsedTimeSinceBase = 0f;
  33. private Material _blendMat;
  34. private ResampleMode _resampleMode;
  35. private string _name = "";
  36. private long _lastTimeStamp = -1;
  37. private int _droppedFrames = 0;
  38. private long _lastDisplayedTimestamp = 0;
  39. private int _frameDisplayedTimer = 0;
  40. private long _currentDisplayedTimestamp = 0;
  41. public int DroppedFrames
  42. {
  43. get { return _droppedFrames; }
  44. }
  45. public int FrameDisplayedTimer
  46. {
  47. get { return _frameDisplayedTimer; }
  48. }
  49. public long BaseTimestamp
  50. {
  51. get { return _baseTimestamp; }
  52. set { _baseTimestamp = value; }
  53. }
  54. public float ElapsedTimeSinceBase
  55. {
  56. get { return _elapsedTimeSinceBase; }
  57. set { _elapsedTimeSinceBase = value; }
  58. }
  59. public float LastT
  60. {
  61. get; private set;
  62. }
  63. public long TextureTimeStamp
  64. {
  65. get; private set;
  66. }
  67. private const string ShaderPropT = "_t";
  68. private const string ShaderPropAftertex = "_AfterTex";
  69. private int _propAfterTex;
  70. private int _propT;
  71. private float _videoFrameRate;
  72. public void OnVideoEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
  73. {
  74. switch (et)
  75. {
  76. case MediaPlayerEvent.EventType.MetaDataReady:
  77. _videoFrameRate = mp.Info.GetVideoFrameRate();
  78. _elapsedTimeSinceBase = 0f;
  79. if (_videoFrameRate > 0f)
  80. {
  81. _elapsedTimeSinceBase = _bufferSize / _videoFrameRate;
  82. }
  83. break;
  84. case MediaPlayerEvent.EventType.Closing:
  85. Reset();
  86. break;
  87. default:
  88. break;
  89. }
  90. }
  91. public Resampler(MediaPlayer player, string name, int bufferSize = 2, ResampleMode resampleMode = ResampleMode.LINEAR)
  92. {
  93. _bufferSize = Mathf.Max(2, bufferSize);
  94. player.Events.AddListener(OnVideoEvent);
  95. _mediaPlayer = player;
  96. Shader blendShader = Shader.Find("AVProVideo/Internal/BlendFrames");
  97. if (blendShader != null)
  98. {
  99. _blendMat = new Material(blendShader);
  100. _propT = Shader.PropertyToID(ShaderPropT);
  101. _propAfterTex = Shader.PropertyToID(ShaderPropAftertex);
  102. }
  103. else
  104. {
  105. Debug.LogError("[AVProVideo] Failed to find BlendFrames shader");
  106. }
  107. _resampleMode = resampleMode;
  108. _name = name;
  109. Debug.Log("[AVProVideo] Resampler " + _name + " started");
  110. }
  111. public Texture[] OutputTexture
  112. {
  113. get { return _outputTexture; }
  114. }
  115. public void Reset()
  116. {
  117. _lastTimeStamp = -1;
  118. _baseTimestamp = 0;
  119. InvalidateBuffer();
  120. }
  121. public void Release()
  122. {
  123. ReleaseRenderTextures();
  124. if (_blendMat != null)
  125. {
  126. if (Application.isPlaying)
  127. {
  128. Material.Destroy(_blendMat);
  129. }
  130. else
  131. {
  132. Material.DestroyImmediate(_blendMat);
  133. }
  134. }
  135. }
  136. private void ReleaseRenderTextures()
  137. {
  138. for (int i = 0; i < _buffer.Count; ++i)
  139. {
  140. for (int j = 0; j < _buffer[i].Length; ++j)
  141. {
  142. if (_buffer[i][j].texture != null)
  143. {
  144. RenderTexture.ReleaseTemporary(_buffer[i][j].texture);
  145. _buffer[i][j].texture = null;
  146. }
  147. }
  148. if (_outputTexture != null && _outputTexture[i] != null)
  149. {
  150. RenderTexture.ReleaseTemporary(_outputTexture[i]);
  151. }
  152. }
  153. _outputTexture = null;
  154. }
  155. private void ConstructRenderTextures()
  156. {
  157. ReleaseRenderTextures();
  158. _buffer.Clear();
  159. _outputTexture = new RenderTexture[_mediaPlayer.TextureProducer.GetTextureCount()];
  160. for (int i = 0; i < _mediaPlayer.TextureProducer.GetTextureCount(); ++i)
  161. {
  162. Texture tex = _mediaPlayer.TextureProducer.GetTexture(i);
  163. _buffer.Add(new TimestampedRenderTexture[_bufferSize]);
  164. for (int j = 0; j < _bufferSize; ++j)
  165. {
  166. _buffer[i][j] = new TimestampedRenderTexture();
  167. }
  168. for (int j = 0; j < _buffer[i].Length; ++j)
  169. {
  170. _buffer[i][j].texture = RenderTexture.GetTemporary(tex.width, tex.height, 0);
  171. _buffer[i][j].timestamp = 0;
  172. _buffer[i][j].used = false;
  173. }
  174. _outputTexture[i] = RenderTexture.GetTemporary(tex.width, tex.height, 0);
  175. _outputTexture[i].filterMode = tex.filterMode;
  176. _outputTexture[i].wrapMode = tex.wrapMode;
  177. _outputTexture[i].anisoLevel = tex.anisoLevel;
  178. // TODO: set up the mips level too?
  179. }
  180. }
  181. private bool CheckRenderTexturesValid()
  182. {
  183. for (int i = 0; i < _mediaPlayer.TextureProducer.GetTextureCount(); ++i)
  184. {
  185. Texture tex = _mediaPlayer.TextureProducer.GetTexture(i);
  186. for (int j = 0; j < _buffer.Count; ++j)
  187. {
  188. if (_buffer[i][j].texture == null || _buffer[i][j].texture.width != tex.width || _buffer[i][j].texture.height != tex.height)
  189. {
  190. return false;
  191. }
  192. }
  193. if (_outputTexture == null || _outputTexture[i] == null || _outputTexture[i].width != tex.width || _outputTexture[i].height != tex.height)
  194. {
  195. return false;
  196. }
  197. }
  198. return true;
  199. }
  200. //finds closest frame that occurs before given index
  201. private int FindBeforeFrameIndex(int frameIdx)
  202. {
  203. if (frameIdx >= _buffer.Count)
  204. {
  205. return -1;
  206. }
  207. int foundFrame = -1;
  208. float smallestDif = float.MaxValue;
  209. int closest = -1;
  210. float smallestElapsed = float.MaxValue;
  211. for (int i = 0; i < _buffer[frameIdx].Length; ++i)
  212. {
  213. if (_buffer[frameIdx][i].used)
  214. {
  215. float elapsed = (_buffer[frameIdx][i].timestamp - _baseTimestamp) / 10000000f;
  216. //keep track of closest after frame, just in case no before frame was found
  217. if (elapsed < smallestElapsed)
  218. {
  219. closest = i;
  220. smallestElapsed = elapsed;
  221. }
  222. float dif = _elapsedTimeSinceBase - elapsed;
  223. if (dif >= 0 && dif < smallestDif)
  224. {
  225. smallestDif = dif;
  226. foundFrame = i;
  227. }
  228. }
  229. }
  230. if (foundFrame < 0)
  231. {
  232. if (closest < 0)
  233. {
  234. return -1;
  235. }
  236. return closest;
  237. }
  238. return foundFrame;
  239. }
  240. private int FindClosestFrame(int frameIdx)
  241. {
  242. if (frameIdx >= _buffer.Count)
  243. {
  244. return -1;
  245. }
  246. int foundPos = -1;
  247. float smallestDif = float.MaxValue;
  248. for (int i = 0; i < _buffer[frameIdx].Length; ++i)
  249. {
  250. if (_buffer[frameIdx][i].used)
  251. {
  252. float elapsed = (_buffer[frameIdx][i].timestamp - _baseTimestamp) / 10000000f;
  253. float dif = Mathf.Abs(_elapsedTimeSinceBase - elapsed);
  254. if (dif < smallestDif)
  255. {
  256. foundPos = i;
  257. smallestDif = dif;
  258. }
  259. }
  260. }
  261. return foundPos;
  262. }
  263. //point update selects closest frame and uses that as output
  264. private void PointUpdate()
  265. {
  266. for (int i = 0; i < _buffer.Count; ++i)
  267. {
  268. int frameIndex = FindClosestFrame(i);
  269. if (frameIndex < 0)
  270. {
  271. continue;
  272. }
  273. _outputTexture[i].DiscardContents();
  274. Graphics.Blit(_buffer[i][frameIndex].texture, _outputTexture[i]);
  275. TextureTimeStamp = _currentDisplayedTimestamp = _buffer[i][frameIndex].timestamp;
  276. }
  277. }
  278. //Updates currently displayed frame
  279. private void SampleFrame(int frameIdx, int bufferIdx)
  280. {
  281. _outputTexture[bufferIdx].DiscardContents();
  282. Graphics.Blit(_buffer[bufferIdx][frameIdx].texture, _outputTexture[bufferIdx]);
  283. TextureTimeStamp = _currentDisplayedTimestamp = _buffer[bufferIdx][frameIdx].timestamp;
  284. }
  285. //Same as sample frame, but does a lerp of the two given frames and outputs that image instead
  286. private void SampleFrames(int bufferIdx, int frameIdx1, int frameIdx2, float t)
  287. {
  288. _blendMat.SetFloat(_propT, t);
  289. _blendMat.SetTexture(_propAfterTex, _buffer[bufferIdx][frameIdx2].texture);
  290. _outputTexture[bufferIdx].DiscardContents();
  291. Graphics.Blit(_buffer[bufferIdx][frameIdx1].texture, _outputTexture[bufferIdx], _blendMat);
  292. TextureTimeStamp = (long)Mathf.Lerp(_buffer[bufferIdx][frameIdx1].timestamp, _buffer[bufferIdx][frameIdx2].timestamp, t);
  293. _currentDisplayedTimestamp = _buffer[bufferIdx][frameIdx1].timestamp;
  294. }
  295. private void LinearUpdate()
  296. {
  297. for (int i = 0; i < _buffer.Count; ++i)
  298. {
  299. //find closest frame
  300. int frameIndex = FindBeforeFrameIndex(i);
  301. //no valid frame, this should never ever happen actually...
  302. if (frameIndex < 0)
  303. {
  304. continue;
  305. }
  306. //resample or just use last frame and set current elapsed time to that frame
  307. float frameElapsed = (_buffer[i][frameIndex].timestamp - _baseTimestamp) / 10000000f;
  308. if (frameElapsed > _elapsedTimeSinceBase)
  309. {
  310. SampleFrame(frameIndex, i);
  311. LastT = -1f;
  312. }
  313. else
  314. {
  315. int next = (frameIndex + 1) % _buffer[i].Length;
  316. float nextElapsed = (_buffer[i][next].timestamp - _baseTimestamp) / 10000000f;
  317. //no larger frame, move elapsed time back a bit since we cant predict the future
  318. if (nextElapsed < frameElapsed)
  319. {
  320. SampleFrame(frameIndex, i);
  321. LastT = 2f;
  322. }
  323. //have a before and after frame, interpolate
  324. else
  325. {
  326. float range = nextElapsed - frameElapsed;
  327. float t = (_elapsedTimeSinceBase - frameElapsed) / range;
  328. SampleFrames(i, frameIndex, next, t);
  329. LastT = t;
  330. }
  331. }
  332. }
  333. }
  334. private void InvalidateBuffer()
  335. {
  336. _elapsedTimeSinceBase = (_bufferSize / 2) / _videoFrameRate;
  337. for (int i = 0; i < _buffer.Count; ++i)
  338. {
  339. for (int j = 0; j < _buffer[i].Length; ++j)
  340. {
  341. _buffer[i][j].used = false;
  342. }
  343. }
  344. _start = _end = 0;
  345. }
  346. private float GuessFrameRate()
  347. {
  348. int fpsCount = 0;
  349. long fps = 0;
  350. for (int k = 0; k < _buffer[0].Length; k++)
  351. {
  352. if (_buffer[0][k].used)
  353. {
  354. // Find the pair with the smallest difference
  355. long smallestDiff = long.MaxValue;
  356. for (int j = k + 1; j < _buffer[0].Length; j++)
  357. {
  358. if (_buffer[0][j].used)
  359. {
  360. long diff = System.Math.Abs(_buffer[0][k].timestamp - _buffer[0][j].timestamp);
  361. if (diff < smallestDiff)
  362. {
  363. smallestDiff = diff;
  364. }
  365. }
  366. }
  367. if (smallestDiff != long.MaxValue)
  368. {
  369. fps += smallestDiff;
  370. fpsCount++;
  371. }
  372. }
  373. }
  374. if (fpsCount > 1)
  375. {
  376. fps /= fpsCount;
  377. }
  378. return 10000000f / (float)fps;
  379. }
  380. public void Update()
  381. {
  382. if (_mediaPlayer.TextureProducer == null)
  383. {
  384. return;
  385. }
  386. //recreate textures if invalid
  387. if (_mediaPlayer.TextureProducer == null || _mediaPlayer.TextureProducer.GetTexture() == null)
  388. {
  389. return;
  390. }
  391. if (!CheckRenderTexturesValid())
  392. {
  393. ConstructRenderTextures();
  394. }
  395. long currentTimestamp = _mediaPlayer.TextureProducer.GetTextureTimeStamp();
  396. //if frame has been updated, do a calculation to estimate dropped frames
  397. if (currentTimestamp != _lastTimeStamp)
  398. {
  399. float dif = Mathf.Abs(currentTimestamp - _lastTimeStamp);
  400. float frameLength = (10000000f / _videoFrameRate);
  401. if (dif > frameLength * 1.1f && dif < frameLength * 3.1f)
  402. {
  403. _droppedFrames += (int)((dif - frameLength) / frameLength + 0.5);
  404. }
  405. _lastTimeStamp = currentTimestamp;
  406. }
  407. //Adding texture to buffer logic
  408. long timestamp = _mediaPlayer.TextureProducer.GetTextureTimeStamp();
  409. bool insertNewFrame = !_mediaPlayer.Control.IsSeeking();
  410. //if buffer is not empty, we need to check if we need to reject the new frame
  411. if (_start != _end || _buffer[0][_end].used)
  412. {
  413. int lastFrame = (_end + _buffer[0].Length - 1) % _buffer[0].Length;
  414. //frame is not new and thus we do not need to store it
  415. if (timestamp == _buffer[0][lastFrame].timestamp)
  416. {
  417. insertNewFrame = false;
  418. }
  419. }
  420. bool bufferWasNotFull = (_start != _end) || (!_buffer[0][_end].used);
  421. if (insertNewFrame)
  422. {
  423. //buffer empty, reset base timestamp to current
  424. if (_start == _end && !_buffer[0][_end].used)
  425. {
  426. _baseTimestamp = timestamp;
  427. }
  428. //update buffer counters, if buffer is full, we get rid of the earliest frame by incrementing the start counter
  429. if (_end == _start && _buffer[0][_end].used)
  430. {
  431. _start = (_start + 1) % _buffer[0].Length;
  432. }
  433. for (int i = 0; i < _mediaPlayer.TextureProducer.GetTextureCount(); ++i)
  434. {
  435. Texture currentTexture = _mediaPlayer.TextureProducer.GetTexture(i);
  436. //store frame info
  437. _buffer[i][_end].texture.DiscardContents();
  438. Graphics.Blit(currentTexture, _buffer[i][_end].texture);
  439. _buffer[i][_end].timestamp = timestamp;
  440. _buffer[i][_end].used = true;
  441. }
  442. _end = (_end + 1) % _buffer[0].Length;
  443. }
  444. bool bufferNotFull = (_start != _end) || (!_buffer[0][_end].used);
  445. if (bufferNotFull)
  446. {
  447. for (int i = 0; i < _buffer.Count; ++i)
  448. {
  449. _outputTexture[i].DiscardContents();
  450. Graphics.Blit(_buffer[i][_start].texture, _outputTexture[i]);
  451. _currentDisplayedTimestamp = _buffer[i][_start].timestamp;
  452. }
  453. }
  454. else
  455. {
  456. // If we don't have a valid frame rate and the buffer is now full, guess the frame rate by looking at the buffered timestamps
  457. if (bufferWasNotFull && _videoFrameRate <= 0f)
  458. {
  459. _videoFrameRate = GuessFrameRate();
  460. _elapsedTimeSinceBase = (_bufferSize / 2) / _videoFrameRate;
  461. }
  462. }
  463. if (_mediaPlayer.Control.IsPaused())
  464. {
  465. InvalidateBuffer();
  466. }
  467. //we always wait until buffer is full before display things, just assign first frame in buffer to output so that the user can see something
  468. if (bufferNotFull)
  469. {
  470. return;
  471. }
  472. if (_mediaPlayer.Control.IsPlaying() && !_mediaPlayer.Control.IsFinished())
  473. {
  474. //correct elapsed time if too far out
  475. long ts = _buffer[0][(_start + _bufferSize / 2) % _bufferSize].timestamp - _baseTimestamp;
  476. double dif = Mathf.Abs(((float)((double)_elapsedTimeSinceBase * 10000000) - ts));
  477. double threshold = (_buffer[0].Length / 2) / _videoFrameRate * 10000000;
  478. if (dif > threshold)
  479. {
  480. _elapsedTimeSinceBase = ts / 10000000f;
  481. }
  482. if (_resampleMode == ResampleMode.POINT)
  483. {
  484. PointUpdate();
  485. }
  486. else if (_resampleMode == ResampleMode.LINEAR)
  487. {
  488. LinearUpdate();
  489. }
  490. _elapsedTimeSinceBase += Time.unscaledDeltaTime;
  491. }
  492. }
  493. public void UpdateTimestamp()
  494. {
  495. if (_lastDisplayedTimestamp != _currentDisplayedTimestamp)
  496. {
  497. _lastDisplayedTimestamp = _currentDisplayedTimestamp;
  498. _frameDisplayedTimer = 0;
  499. }
  500. _frameDisplayedTimer++;
  501. }
  502. }
  503. }