316 lines
11 KiB
C#
316 lines
11 KiB
C#
// NOTE: We only allow this script to compile in editor so we can easily check for compilation issues
|
|
#if (UNITY_EDITOR || (UNITY_STANDALONE_WIN || UNITY_WSA_10_0))
|
|
|
|
#define AVPROVIDEO_FIXREGRESSION_TEXTUREQUALITY_UNITY542
|
|
#if UNITY_WSA_10 || ENABLE_IL2CPP
|
|
#define AVPROVIDEO_MARSHAL_RETURN_BOOL
|
|
#endif
|
|
#if UNITY_2019_3_OR_NEWER && !UNITY_2020_1_OR_NEWER
|
|
#define AVPROVIDEO_FIX_UPDATEEXTERNALTEXTURE_LEAK
|
|
#endif
|
|
|
|
using UnityEngine;
|
|
using System.Runtime.InteropServices;
|
|
using System.Collections.Generic;
|
|
using System;
|
|
using System.Text;
|
|
|
|
#if NETFX_CORE
|
|
using Windows.Storage.Streams;
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace RenderHeads.Media.AVProVideo
|
|
{
|
|
/// <summary>
|
|
/// Windows desktop and UWP implementation of BaseMediaPlayer
|
|
/// </summary>
|
|
public /*sealed*/ partial class WindowsMediaPlayer : BaseMediaPlayer
|
|
{
|
|
|
|
#region IBufferedDisplay Implementation
|
|
|
|
private BufferedFrameSelectionMode _frameSelectionMode = BufferedFrameSelectionMode.None;
|
|
private bool _pauseOnPrerollComplete = false;
|
|
private IBufferedDisplay _masterDisplay;
|
|
private IBufferedDisplay[] _slaveDisplays;
|
|
private double _displayClockTime = 0.0;
|
|
private double _timeAccumulation = 0.0;
|
|
private bool _needsInitialFrame = true;
|
|
|
|
private void FlushFrameBuffering(bool releaseTexture)
|
|
{
|
|
if (_frameSelectionMode == BufferedFrameSelectionMode.None) return;
|
|
|
|
if (releaseTexture && _textureFrame.internalNativePointer != System.IntPtr.Zero)
|
|
{
|
|
Native.UnlockTextureFrame(_instance, ref _textureFrame);
|
|
_textureFrame.internalNativePointer = System.IntPtr.Zero;
|
|
_textureFrame.texturePointer = System.IntPtr.Zero;
|
|
}
|
|
|
|
Native.FlushFrameBuffering(_instance);
|
|
|
|
// Native _pauseOnPrerollComplete needs to be reset
|
|
//Native.SetFrameBufferingEnabled(_instance, (_frameSelectionMode != BufferedFrameSelectionMode.None), _pauseOnPrerollComplete);
|
|
|
|
_needsInitialFrame = true;
|
|
_timeAccumulation = 0.0;
|
|
}
|
|
|
|
internal override long InternalUpdateBufferedDisplay()
|
|
{
|
|
BufferedFramesState state = GetBufferedFramesState();
|
|
if (state.bufferedFrameCount > 0)
|
|
{
|
|
if (_frameSelectionMode == BufferedFrameSelectionMode.NewestFrame)
|
|
{
|
|
SetBufferedDisplayTime(_frameSelectionMode, -1, false);
|
|
}
|
|
else if (_frameSelectionMode == BufferedFrameSelectionMode.OldestFrame)
|
|
{
|
|
SetBufferedDisplayTime(_frameSelectionMode, -1, false);
|
|
}
|
|
else if (_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTime ||
|
|
_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTimeVsynced)
|
|
{
|
|
// Only start consuming frames on these conditions
|
|
bool needsInitialFrame = (_textureFrame.texturePointer == System.IntPtr.Zero || _needsInitialFrame);
|
|
bool playingBufferedFrames = (IsPrerollComplete() && (IsPlaying() || (!IsPlaying() && IsFinished())));
|
|
if (needsInitialFrame || playingBufferedFrames)
|
|
{
|
|
if (needsInitialFrame)
|
|
{
|
|
if (SetBufferedDisplayTime(BufferedFrameSelectionMode.OldestFrame, -1, true))
|
|
{
|
|
_displayClockTime = _textureFrame.timeStamp;
|
|
_needsInitialFrame = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: run without vsync, just show next frame (use media clock for present?)
|
|
// use our own clock...
|
|
const double SecondsToHNS = 10000000.0;
|
|
double videoFrameDuration = SecondsToHNS / (double)GetVideoFrameRate();
|
|
long videoDuration = (long)Math.Floor(SecondsToHNS * GetDuration());
|
|
long lastFrameTime = Math.Max(videoDuration, state.maxTimeStamp);
|
|
double delta = SecondsToHNS * Time.deltaTime;
|
|
|
|
if (_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTimeVsynced && QualitySettings.vSyncCount > 0)
|
|
{
|
|
// Since we're running with vsync enabled, the MINIMUM elapsed time will be 1 monitor refresh (multiplied by QualitySettings.vSyncCount)
|
|
double monitorDuration = (QualitySettings.vSyncCount * SecondsToHNS) / (double)Screen.currentResolution.refreshRate;
|
|
|
|
int wholeFrames = (int)System.Math.Floor(_timeAccumulation / monitorDuration);
|
|
wholeFrames = System.Math.Max(1, wholeFrames);
|
|
delta = monitorDuration * wholeFrames;
|
|
|
|
//LogBufferState();
|
|
if (wholeFrames > 1)
|
|
{
|
|
//Debug.Log(Time.frameCount + "] " + Time.deltaTime + " " + wholeFrames + " " + _timeAccumulation + " " + _timeAccumulation / SecondsToHNS);
|
|
//LogBufferState();
|
|
}
|
|
|
|
_timeAccumulation += (Time.deltaTime * SecondsToHNS) - delta;
|
|
|
|
//delta = monitorDuration;
|
|
|
|
/*double actualFrameDuration = Time.deltaTime * SecondsToHNS;
|
|
double idealFrameTimeDifference = (actualFrameDuration);// - minMonitorDuration);
|
|
if (idealFrameTimeDifference > (minMonitorDuration / 2))
|
|
{
|
|
int droppedFrames = (int)Math.Round(idealFrameTimeDifference / minMonitorDuration);
|
|
//Debug.Log(Time.maximumDeltaTime + " " + Time.deltaTime + " " + actualFrameDuration + " " + idealFrameTimeDifference + " = " + droppedFrames);
|
|
delta += minMonitorDuration * droppedFrames;
|
|
|
|
//LogBufferState();
|
|
}
|
|
else
|
|
{
|
|
//Debug.Log(Time.deltaTime);
|
|
}
|
|
// If we're running slower than this or there is a frame drop, the elapsed time will be a multiple
|
|
// of the monitor refresh rate
|
|
*/
|
|
}
|
|
|
|
_displayClockTime += delta;
|
|
|
|
int multiple = (int)videoFrameDuration;
|
|
long snappedFrameTime = (long)Math.Floor(_displayClockTime / multiple) * multiple;
|
|
if (_isLooping && snappedFrameTime > lastFrameTime)
|
|
{
|
|
snappedFrameTime %= lastFrameTime;
|
|
_needsInitialFrame = true;
|
|
}
|
|
else
|
|
{
|
|
snappedFrameTime = Math.Min(snappedFrameTime, lastFrameTime);
|
|
}
|
|
|
|
if (System.Math.Abs(snappedFrameTime - _textureFrame.timeStamp) > 1000)
|
|
{
|
|
//Debug.Log("1 " + _displayClockTime + " > " + snappedFrameTime + " d:" + delta);
|
|
//LogBufferState();
|
|
|
|
if (_needsInitialFrame)
|
|
{
|
|
if (SetBufferedDisplayTime(BufferedFrameSelectionMode.OldestFrame, -1, true))
|
|
{
|
|
_displayClockTime = _textureFrame.timeStamp;
|
|
//Debug.Log("initial: " + _displayClockTime);
|
|
_needsInitialFrame = false;
|
|
}
|
|
}
|
|
else if (!SetBufferedDisplayTime(_frameSelectionMode, snappedFrameTime, false))
|
|
{
|
|
//Debug.LogWarning("[AVProVideo] failed to set time at " + snappedFrameTime);
|
|
//LogBufferState();
|
|
|
|
// Try to snap to oldest buffered time
|
|
_displayClockTime = (state.minTimeStamp + state.maxTimeStamp) / 2.0;
|
|
snappedFrameTime = (long)Math.Floor(_displayClockTime / multiple) * multiple;
|
|
if (_isLooping && snappedFrameTime > lastFrameTime)
|
|
{
|
|
snappedFrameTime %= lastFrameTime;
|
|
}
|
|
else
|
|
{
|
|
snappedFrameTime = Math.Min(snappedFrameTime, lastFrameTime);
|
|
}
|
|
|
|
if (SetBufferedDisplayTime(BufferedFrameSelectionMode.FromExternalTimeClosest, snappedFrameTime, false))
|
|
{
|
|
_displayClockTime = _textureFrame.timeStamp;
|
|
//Debug.LogWarning("[AVProVideo] Good set: " + _displayClockTime);
|
|
}
|
|
else
|
|
{
|
|
//Debug.LogWarning("[AVProVideo] Failed to display frame time " + snappedFrameTime);
|
|
//LogBufferState();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (_frameSelectionMode == BufferedFrameSelectionMode.FromExternalTime)
|
|
{
|
|
if (_masterDisplay != null)
|
|
{
|
|
// Use the time from the master
|
|
long timeStamp = _masterDisplay.UpdateBufferedDisplay();
|
|
if (timeStamp != GetTextureTimeStamp())
|
|
{
|
|
if (!SetBufferedDisplayTime(BufferedFrameSelectionMode.FromExternalTimeClosest, timeStamp, false))
|
|
{
|
|
Debug.LogWarning("[AVProVideo] Failed to display frame using external clock at time " + timeStamp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return GetTextureTimeStamp();
|
|
}
|
|
|
|
private void LogBufferState()
|
|
{
|
|
BufferedFramesState state = GetBufferedFramesState();
|
|
long timeStamp = GetTextureTimeStamp();
|
|
string result = string.Format("[AVProVideo] {4} - {2},{3}\t\t{0}-{1} ({5})", state.minTimeStamp, state.maxTimeStamp, state.bufferedFrameCount, state.freeFrameCount, timeStamp, Time.deltaTime);
|
|
Debug.Log(result);
|
|
}
|
|
|
|
private bool SetBufferedDisplayTime(BufferedFrameSelectionMode mode, long timeOfDesiredFrameToDisplay, bool ignorePreroll)
|
|
{
|
|
bool result = false;
|
|
//if (!_isPaused)
|
|
{
|
|
result = Native.LockTextureFrame(_instance, mode, timeOfDesiredFrameToDisplay, ref _textureFrame, ignorePreroll);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public override BufferedFramesState GetBufferedFramesState()
|
|
{
|
|
BufferedFramesState state = new BufferedFramesState();
|
|
Native.GetBufferedFramesState(_instance, ref state);
|
|
return state;
|
|
}
|
|
|
|
public override void SetBufferedDisplayMode(BufferedFrameSelectionMode mode, IBufferedDisplay master = null)
|
|
{
|
|
_frameSelectionMode = mode;
|
|
_masterDisplay = master;
|
|
UpdateBufferedDisplay();
|
|
}
|
|
|
|
public override void SetBufferedDisplayOptions(bool pauseOnPrerollComplete)
|
|
{
|
|
_pauseOnPrerollComplete = pauseOnPrerollComplete;
|
|
Native.SetFrameBufferingEnabled(_instance, (_frameSelectionMode != BufferedFrameSelectionMode.None), _pauseOnPrerollComplete);
|
|
}
|
|
|
|
public override void SetSlaves(IBufferedDisplay[] slaves)
|
|
{
|
|
foreach (IBufferedDisplay slave in slaves)
|
|
{
|
|
slave.SetBufferedDisplayMode(BufferedFrameSelectionMode.FromExternalTime, this);
|
|
}
|
|
_slaveDisplays = slaves;
|
|
}
|
|
|
|
private bool IsPrerollComplete()
|
|
{
|
|
bool result = true;
|
|
|
|
if (GetBufferedFramesState().prerolledCount <= 0)
|
|
{
|
|
result = false;
|
|
}
|
|
if (_slaveDisplays != null && result)
|
|
{
|
|
foreach (IBufferedDisplay slave in _slaveDisplays)
|
|
{
|
|
if (slave.GetBufferedFramesState().prerolledCount <= 0)
|
|
{
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private partial struct Native
|
|
{
|
|
[DllImport("AVProVideo")]
|
|
public static extern bool GetBufferedFramesState(System.IntPtr playerInstance, ref BufferedFramesState state);
|
|
|
|
[DllImport("AVProVideo")]
|
|
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
|
[return: MarshalAs(UnmanagedType.I1)]
|
|
#endif
|
|
public static extern bool LockTextureFrame(System.IntPtr instance, BufferedFrameSelectionMode mode, long time, ref TextureFrame textureFrame, bool ignorePreroll);
|
|
|
|
[DllImport("AVProVideo")]
|
|
public static extern void UnlockTextureFrame(System.IntPtr instance, ref TextureFrame textureFrame);
|
|
|
|
[DllImport("AVProVideo")]
|
|
public static extern void ReleaseTextureFrame(System.IntPtr instance, ref TextureFrame textureFrame);
|
|
|
|
[DllImport("AVProVideo")]
|
|
public static extern void FlushFrameBuffering(System.IntPtr instance);
|
|
}
|
|
|
|
#endregion // IBufferedDisplay Implementation
|
|
|
|
}
|
|
}
|
|
#endif |