Highly optimized video decoding
still some problems with looping videos
This commit is contained in:
1 parent
82b650416f
commit
5245998662
3 files changed
+415
-286
No files matched your search
@@ -18,9 +18,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Vocaluxe.Base;
|
||||
using VocaluxeLib.Draw;
|
||||
|
||||
namespace Vocaluxe.Lib.Video.Acinerella
|
||||
@@ -28,46 +25,54 @@ namespace Vocaluxe.Lib.Video.Acinerella
|
||||
class CDecoder
|
||||
{
|
||||
private readonly Stopwatch _LoopTimer = new Stopwatch();
|
||||
private readonly Object _MutexSyncSignals = new Object();
|
||||
|
||||
private float _LastShownTime; // time if the last shown frame in s
|
||||
|
||||
private float _Time;
|
||||
private float _LastShownTime = -1f; // time if the last shown frame in s
|
||||
private float _Gap;
|
||||
private float _Start;
|
||||
private bool _Loop;
|
||||
|
||||
private float _SetTime;
|
||||
private float _SetGap;
|
||||
private float _SetStart;
|
||||
private bool _SetSkip;
|
||||
|
||||
private float _SkipTime; // = Start + VideoGap
|
||||
|
||||
private string _FileName; // current video file name
|
||||
private bool _Paused;
|
||||
private float _LoopTime;
|
||||
|
||||
private CDecoderThread _Thread;
|
||||
|
||||
public CDecoder() {}
|
||||
|
||||
public float Length { get; private set; }
|
||||
|
||||
public bool Loop { private get; set; }
|
||||
|
||||
public bool Finished { get; private set; }
|
||||
|
||||
public CDecoder()
|
||||
{
|
||||
Length = 0;
|
||||
Finished = true;
|
||||
}
|
||||
|
||||
public bool Loop
|
||||
{
|
||||
get { return _Thread.Loop; }
|
||||
set
|
||||
{
|
||||
{
|
||||
_Thread.Loop = value;
|
||||
if (value)
|
||||
{
|
||||
_LoopTime = _Thread._RequestTime;
|
||||
_LoopTimer.Restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Paused
|
||||
{
|
||||
set
|
||||
{
|
||||
lock (_MutexSyncSignals)
|
||||
if (_Paused == value)
|
||||
return;
|
||||
_Paused = value;
|
||||
if (_Paused)
|
||||
{
|
||||
_Paused = value;
|
||||
if (_Paused)
|
||||
_LoopTimer.Stop();
|
||||
else
|
||||
_LoopTimer.Start();
|
||||
_LoopTimer.Stop();
|
||||
_Thread.Pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
_LoopTimer.Start();
|
||||
_Thread.Resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,35 +85,15 @@ namespace Vocaluxe.Lib.Video.Acinerella
|
||||
if (!File.Exists(fileName))
|
||||
return false;
|
||||
|
||||
_FileName = fileName;
|
||||
|
||||
//Do this here as one may want to get the length afterwards!
|
||||
IntPtr pInstance = _LoadFile();
|
||||
if (pInstance == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
|
||||
_Thread = new CDecoderThread(Path.GetFileName(fileName), pInstance);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Open the file and get the length.
|
||||
private IntPtr _LoadFile()
|
||||
{
|
||||
try
|
||||
_Thread = new CDecoderThread();
|
||||
if (_Thread.LoadFile(fileName))
|
||||
{
|
||||
IntPtr pInstance = CAcinerella.AcInit();
|
||||
CAcinerella.AcOpen2(pInstance, _FileName);
|
||||
|
||||
var instance = (SACInstance)Marshal.PtrToStructure(pInstance, typeof(SACInstance));
|
||||
Length = instance.Info.Duration / 1000f;
|
||||
bool ok = instance.Opened;
|
||||
if (ok)
|
||||
return pInstance;
|
||||
Length = _Thread.Length;
|
||||
Finished = false;
|
||||
return _Thread.Start();
|
||||
}
|
||||
catch (Exception) {}
|
||||
CLog.LogError("Error opening video file: " + _FileName);
|
||||
return IntPtr.Zero;
|
||||
_Thread = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
@@ -116,45 +101,61 @@ namespace Vocaluxe.Lib.Video.Acinerella
|
||||
if (_Thread != null)
|
||||
_Thread.Stop();
|
||||
Length = 0;
|
||||
Finished = true;
|
||||
}
|
||||
|
||||
public bool GetFrame(ref CTexture frame, float time, out float videoTime)
|
||||
{
|
||||
if (Finished)
|
||||
{
|
||||
videoTime = Length - _Gap;
|
||||
return false;
|
||||
}
|
||||
if (Loop)
|
||||
{
|
||||
lock (_MutexSyncSignals)
|
||||
time = _LoopTime + _LoopTimer.ElapsedMilliseconds / 1000f;
|
||||
if (time >= Length)
|
||||
{
|
||||
_SetTime += _LoopTimer.ElapsedMilliseconds / 1000f;
|
||||
do
|
||||
{
|
||||
time -= Length;
|
||||
} while (time >= Length);
|
||||
_LoopTime = time;
|
||||
_LoopTimer.Restart();
|
||||
}
|
||||
}
|
||||
else if (Math.Abs(_SetTime - time) >= 1f / 1000f) //Check 1 ms difference
|
||||
else
|
||||
time += _Gap;
|
||||
_Thread.SyncTime(time);
|
||||
|
||||
if (Math.Abs(_LastShownTime - time) < 1f / 1000 && frame != null) //Check 1 ms difference
|
||||
{
|
||||
lock (_MutexSyncSignals)
|
||||
{
|
||||
_SetTime = time;
|
||||
}
|
||||
}
|
||||
else if (Math.Abs(_LastShownTime - time) < 1f / 1000 && frame != null) //Check 1 ms difference
|
||||
{
|
||||
videoTime = _LastShownTime;
|
||||
videoTime = _LastShownTime - _Gap;
|
||||
return true;
|
||||
}
|
||||
_UploadNewFrame(ref frame);
|
||||
videoTime = _LastShownTime;
|
||||
CDecoderThread.EFrameState state = _Thread.GetFrame(ref frame, ref time);
|
||||
switch (state)
|
||||
{
|
||||
case CDecoderThread.EFrameState.EndFrame:
|
||||
Finished = true;
|
||||
videoTime = Length - _Gap;
|
||||
break;
|
||||
case CDecoderThread.EFrameState.ValidFrame:
|
||||
_LastShownTime = time;
|
||||
videoTime = time - _Gap;
|
||||
break;
|
||||
default:
|
||||
videoTime = _LastShownTime - _Gap;
|
||||
break;
|
||||
}
|
||||
return frame != null;
|
||||
}
|
||||
|
||||
public bool Skip(float start, float gap)
|
||||
{
|
||||
lock (_MutexSyncSignals)
|
||||
{
|
||||
_SetStart = start;
|
||||
_SetGap = gap;
|
||||
_SetSkip = true;
|
||||
_NoMoreFrames = false;
|
||||
Finished = false;
|
||||
}
|
||||
Finished = false;
|
||||
_Gap = gap;
|
||||
_Thread.Skip(start + gap);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,244 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Vocaluxe.Base;
|
||||
using VocaluxeLib.Draw;
|
||||
|
||||
namespace Vocaluxe.Lib.Video.Acinerella
|
||||
{
|
||||
//This class describes a thread decoding a video
|
||||
//All public methods are meant to be called from "reader" thread only
|
||||
//Most others are to be called by this thread (_Thread instance) only!
|
||||
class CDecoderThread
|
||||
{
|
||||
private readonly Thread _Thread;
|
||||
private readonly Object _MutexFramebuffer = new Object();
|
||||
private bool isStarted;
|
||||
public enum EFrameState
|
||||
{
|
||||
ValidFrame,
|
||||
InvalidFrame,
|
||||
EndFrame
|
||||
}
|
||||
|
||||
private IntPtr _Instance; // acinerella instance
|
||||
private Thread _Thread;
|
||||
|
||||
private IntPtr _Instance = IntPtr.Zero; // acinerella instance
|
||||
private IntPtr _Videodecoder = IntPtr.Zero; // acinerella video decoder instance
|
||||
|
||||
private readonly CFramebuffer _Framebuffer;
|
||||
private float _LastDecodedTime; // time of last decoded frame
|
||||
// time of last requested frame aka current should-be position (_LastDecodedTime should be >=_RequestTime)
|
||||
//HAS to be < Length
|
||||
public float _RequestTime { get; private set; }
|
||||
private bool _Paused;
|
||||
|
||||
private String _FileName;
|
||||
private float _FrameDuration; // frame time in s
|
||||
private int _Width;
|
||||
private int _Height;
|
||||
|
||||
private bool _RequestSkip;
|
||||
private bool _Terminated;
|
||||
private bool _FrameAvailable;
|
||||
private bool _NoMoreFrames;
|
||||
private readonly AutoResetEvent _EvWakeUp = new AutoResetEvent(false);
|
||||
private readonly AutoResetEvent _EvPause = new AutoResetEvent(false);
|
||||
private readonly AutoResetEvent _EvNoMoreFrames = new AutoResetEvent(false);
|
||||
private bool _IsSleeping;
|
||||
private int _WaitCount;
|
||||
|
||||
public CDecoderThread(String name, IntPtr instance)
|
||||
public float Length { get; private set; }
|
||||
public bool Loop { get; set; }
|
||||
|
||||
public CDecoderThread()
|
||||
{
|
||||
_Framebuffer = new CFramebuffer(10);
|
||||
_Instance = instance;
|
||||
_Thread = new Thread(_Execute) {Priority = ThreadPriority.Normal, Name = name};
|
||||
}
|
||||
|
||||
//Open the file and get the length.
|
||||
public bool LoadFile(String fileName)
|
||||
{
|
||||
_FileName = fileName;
|
||||
try
|
||||
{
|
||||
_Instance = CAcinerella.AcInit();
|
||||
CAcinerella.AcOpen2(_Instance, fileName);
|
||||
|
||||
var instance = (SACInstance)Marshal.PtrToStructure(_Instance, typeof(SACInstance));
|
||||
Length = instance.Info.Duration / 1000f;
|
||||
bool ok = instance.Opened && Length > 0.001f;
|
||||
if (ok)
|
||||
return true;
|
||||
_Free();
|
||||
}
|
||||
catch (Exception) {}
|
||||
CLog.LogError("Error opening video file: " + _FileName);
|
||||
_Instance = IntPtr.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Start()
|
||||
{
|
||||
if (_Instance == IntPtr.Zero)
|
||||
{
|
||||
CLog.LogError("Tried to start a video file that is not open: " + _FileName);
|
||||
return false;
|
||||
}
|
||||
if (_Thread != null)
|
||||
{
|
||||
CLog.LogError("Tried to start a video file that is already started: " + _FileName);
|
||||
return false;
|
||||
}
|
||||
_RequestTime = 0f;
|
||||
_Thread = new Thread(_Execute) {Priority = ThreadPriority.Normal, Name = Path.GetFileName(_FileName)};
|
||||
_Thread.Start();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_Terminated = true;
|
||||
_EvPause.Set();
|
||||
_EvWakeUp.Set();
|
||||
_EvNoMoreFrames.Set();
|
||||
}
|
||||
|
||||
private bool _DoOpen()
|
||||
public void Pause()
|
||||
{
|
||||
if (_Paused)
|
||||
return;
|
||||
_EvPause.Reset();
|
||||
_Paused = true;
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
if (!_Paused)
|
||||
return;
|
||||
_Paused = false;
|
||||
_EvPause.Set();
|
||||
}
|
||||
|
||||
public void Skip(float time)
|
||||
{
|
||||
//TODO: Problem: This is not threadsave
|
||||
//Scenario: Clear buffer from main thread, decoder is in _Decode or _Copy
|
||||
//Result: Cleared buffer contains 1 old item. This is a problem when skipping back: E.g. Loop:
|
||||
//Old item has time=Length->Will not get removed
|
||||
//Workaround: Check in _FindFrame to remove first old item
|
||||
_Framebuffer.Clear();
|
||||
|
||||
|
||||
_RequestTime = time;
|
||||
_RequestSkip = true;
|
||||
_EvNoMoreFrames.Set();
|
||||
}
|
||||
|
||||
private CFramebuffer.CFrame _FindFrame(float now)
|
||||
{
|
||||
CFramebuffer.CFrame result = null;
|
||||
float maxEnd = now - _FrameDuration * 2; //Get only frames younger than 2
|
||||
CFramebuffer.CFrame frame;
|
||||
bool first = true;
|
||||
_Framebuffer.ResetStack();
|
||||
while ((frame = _Framebuffer.Pop()) != null)
|
||||
{
|
||||
float frameEnd = frame.Time + _FrameDuration;
|
||||
//Don't show frames that are shown during or after now
|
||||
if (frameEnd >= now)
|
||||
{
|
||||
//All following frames are after that one -> BREAK
|
||||
if (!first)
|
||||
break;
|
||||
//Well... Should be. Workaround:
|
||||
CFramebuffer.CFrame nextFrame = _Framebuffer.Pop();
|
||||
if (nextFrame == null || nextFrame.Time > frame.Time)
|
||||
break;
|
||||
frame.SetRead(); //Discard wrong frame
|
||||
frame = nextFrame;
|
||||
//Replicate calculations
|
||||
frameEnd = frame.Time + _FrameDuration;
|
||||
if (frameEnd >= now)
|
||||
break;
|
||||
}
|
||||
first = false;
|
||||
//Get the last(newest) possible frame and skip the rest to force in-order showing
|
||||
if (frameEnd <= maxEnd)
|
||||
{
|
||||
//Frame is to old -> Discard
|
||||
frame.SetRead();
|
||||
}
|
||||
else
|
||||
{
|
||||
maxEnd = frameEnd;
|
||||
result = frame;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//Time should be < Length
|
||||
public void SyncTime(float time)
|
||||
{
|
||||
if (_Thread == null)
|
||||
return; //Not initialized
|
||||
while (time >= Length)
|
||||
time -= Length;
|
||||
if (_RequestTime - time >= 1f / 1000f)
|
||||
{
|
||||
//Jump back more than 1ms
|
||||
//Will mostly happen in loops when looped through
|
||||
Skip(time);
|
||||
}
|
||||
else if (time - _RequestTime >= 1f / 1000f)
|
||||
{
|
||||
//we were more than 1 ms to slow -> Jump forward
|
||||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
if (Loop && _RequestTime == 0f)
|
||||
// ReSharper restore CompareOfFloatsByEqualityOperator
|
||||
{
|
||||
//Check if we had a loop
|
||||
_Framebuffer.ResetStack();
|
||||
CFramebuffer.CFrame frame;
|
||||
while ((frame = _Framebuffer.Pop()) != null)
|
||||
{
|
||||
if (frame.Time + _FrameDuration >= time)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_RequestTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
public EFrameState GetFrame(ref CTexture frame, ref float time)
|
||||
{
|
||||
EFrameState result;
|
||||
CFramebuffer.CFrame curFrame = _FindFrame(time);
|
||||
|
||||
if (curFrame != null)
|
||||
{
|
||||
CDraw.UpdateOrAddTexture(ref frame, _Width, _Height, curFrame.Data);
|
||||
if (!_Paused)
|
||||
curFrame.SetRead();
|
||||
time = curFrame.Time;
|
||||
result = (frame != null) ? EFrameState.ValidFrame : EFrameState.InvalidFrame;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_NoMoreFrames && _Framebuffer.IsEmpty())
|
||||
result = EFrameState.EndFrame;
|
||||
else
|
||||
result = EFrameState.InvalidFrame;
|
||||
}
|
||||
if (_IsSleeping)
|
||||
{
|
||||
_IsSleeping = false;
|
||||
_EvWakeUp.Set();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool _OpenVideoStream()
|
||||
{
|
||||
int videoStreamIndex;
|
||||
SACDecoder videodecoder;
|
||||
@@ -57,16 +250,12 @@ namespace Vocaluxe.Lib.Video.Acinerella
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CLog.LogError("Error opening video file (can't find decoder): " + _Thread.Name);
|
||||
_Free();
|
||||
CLog.LogError("Error opening video file (can't find decoder): " + _FileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (videoStreamIndex < 0)
|
||||
{
|
||||
_Free();
|
||||
return false;
|
||||
}
|
||||
|
||||
_Width = videodecoder.StreamInfo.VideoInfo.FrameWidth;
|
||||
_Height = videodecoder.StreamInfo.VideoInfo.FrameHeight;
|
||||
@@ -74,14 +263,12 @@ namespace Vocaluxe.Lib.Video.Acinerella
|
||||
if (videodecoder.StreamInfo.VideoInfo.FramesPerSecond > 0)
|
||||
_FrameDuration = 1f / (float)videodecoder.StreamInfo.VideoInfo.FramesPerSecond;
|
||||
|
||||
_LastDecodedTime = 0f;
|
||||
_SetTime = 0f;
|
||||
|
||||
_Framebuffer.Init(_Width * _Height * 4);
|
||||
_FrameAvailable = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
//Just call this if thread is not alive
|
||||
private void _Free()
|
||||
{
|
||||
if (_Videodecoder != IntPtr.Zero)
|
||||
@@ -94,50 +281,108 @@ namespace Vocaluxe.Lib.Video.Acinerella
|
||||
}
|
||||
}
|
||||
|
||||
// Skip to a given time (in s)
|
||||
private void _Skip()
|
||||
{
|
||||
_SkipTime = _Start + _Gap;
|
||||
|
||||
if (_SkipTime < 0)
|
||||
_SkipTime = 0;
|
||||
float skipTime = _RequestTime;
|
||||
if (skipTime < 0 || skipTime >= Length)
|
||||
skipTime = 0;
|
||||
|
||||
try
|
||||
{
|
||||
CAcinerella.AcSeek(_Videodecoder, -1, (Int64)(_SkipTime * 1000f));
|
||||
CAcinerella.AcSeek(_Videodecoder, -1, (Int64)(skipTime * 1000f));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CLog.LogError("Error seeking video file \"" + _FileName + "\": " + e.Message);
|
||||
}
|
||||
_LastDecodedTime = _SkipTime;
|
||||
_LastDecodedTime = skipTime;
|
||||
|
||||
lock (_MutexFramebuffer)
|
||||
_FrameAvailable = false;
|
||||
}
|
||||
|
||||
private void _Decode()
|
||||
{
|
||||
const int minFrameDropCount = 4;
|
||||
|
||||
if (_Paused || _NoMoreFrames)
|
||||
return;
|
||||
|
||||
float videoTime = _RequestTime;
|
||||
float timeDifference = videoTime - _LastDecodedTime;
|
||||
|
||||
bool dropFrame = timeDifference >= (minFrameDropCount - 1) * _FrameDuration;
|
||||
|
||||
bool hasFrameDecoded = false;
|
||||
if (dropFrame)
|
||||
{
|
||||
_Framebuffer.Clear();
|
||||
try
|
||||
{
|
||||
var frameDropCount = (int)(timeDifference / _FrameDuration);
|
||||
hasFrameDecoded = CAcinerella.AcSkipFrames(_Instance, _Videodecoder, frameDropCount);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CLog.LogError("Error AcSkipFrame " + _FileName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasFrameDecoded)
|
||||
{
|
||||
try
|
||||
{
|
||||
hasFrameDecoded = CAcinerella.AcGetFrame(_Instance, _Videodecoder);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CLog.LogError("Error AcGetFrame " + _FileName);
|
||||
}
|
||||
}
|
||||
if (hasFrameDecoded)
|
||||
_FrameAvailable = true;
|
||||
else
|
||||
{
|
||||
if (Loop)
|
||||
{
|
||||
_RequestTime = 0;
|
||||
_Skip();
|
||||
}
|
||||
else
|
||||
_NoMoreFrames = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void _CopyDecodedFrameToBuffer()
|
||||
{
|
||||
var videodecoder = (SACDecoder)Marshal.PtrToStructure(_Videodecoder, typeof(SACDecoder));
|
||||
if (videodecoder.Buffer != IntPtr.Zero)
|
||||
{
|
||||
_LastDecodedTime = (float)videodecoder.Timecode;
|
||||
_Framebuffer.Put(videodecoder.Buffer, _LastDecodedTime);
|
||||
}
|
||||
_FrameAvailable = false;
|
||||
}
|
||||
|
||||
private void _Execute()
|
||||
{
|
||||
if (!_DoOpen())
|
||||
if (!_OpenVideoStream())
|
||||
{
|
||||
_Free();
|
||||
Stop();
|
||||
}
|
||||
|
||||
while (!_Terminated)
|
||||
{
|
||||
bool skip;
|
||||
lock (_MutexSyncSignals)
|
||||
if (_Paused)
|
||||
_EvPause.WaitOne();
|
||||
if (_NoMoreFrames)
|
||||
_EvNoMoreFrames.WaitOne();
|
||||
if (_RequestSkip)
|
||||
{
|
||||
_Time = _SetTime;
|
||||
skip = _SetSkip;
|
||||
_SetSkip = false;
|
||||
_Gap = _SetGap;
|
||||
_Start = _SetStart;
|
||||
_Loop = Loop;
|
||||
}
|
||||
|
||||
if (skip)
|
||||
_RequestSkip = false;
|
||||
_NoMoreFrames = false;
|
||||
_Skip();
|
||||
}
|
||||
|
||||
if (!_FrameAvailable)
|
||||
_Decode();
|
||||
@@ -146,9 +391,9 @@ namespace Vocaluxe.Lib.Video.Acinerella
|
||||
{
|
||||
if (!_Framebuffer.IsFull())
|
||||
{
|
||||
_Copy();
|
||||
_CopyDecodedFrameToBuffer();
|
||||
_WaitCount = 0;
|
||||
Thread.Sleep(1);
|
||||
Thread.Sleep(5);
|
||||
}
|
||||
else if (_WaitCount > 3)
|
||||
{
|
||||
@@ -165,148 +410,5 @@ namespace Vocaluxe.Lib.Video.Acinerella
|
||||
|
||||
_Free();
|
||||
}
|
||||
|
||||
private void _Decode()
|
||||
{
|
||||
const int minFrameDropCount = 4;
|
||||
|
||||
if (_Paused || _NoMoreFrames)
|
||||
return;
|
||||
|
||||
float videoTime = _Time + _Gap;
|
||||
float timeDifference = videoTime - _LastDecodedTime;
|
||||
|
||||
bool dropFrame = timeDifference >= (minFrameDropCount - 1) * _FrameDuration;
|
||||
|
||||
if (_Terminated)
|
||||
return;
|
||||
|
||||
bool frameFinished = false;
|
||||
if (dropFrame)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (videoTime >= Length)
|
||||
{
|
||||
if (_Loop)
|
||||
{
|
||||
lock (_MutexSyncSignals)
|
||||
{
|
||||
_Start = 0;
|
||||
_Gap = 0f;
|
||||
_SetTime = 0;
|
||||
}
|
||||
_Skip();
|
||||
}
|
||||
else
|
||||
{
|
||||
_NoMoreFrames = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var frameDropCount = (int)(timeDifference / _FrameDuration);
|
||||
frameFinished = CAcinerella.AcSkipFrames(_Instance, _Videodecoder, frameDropCount);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CLog.LogError("Error AcSkipFrame " + _Thread.Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!frameFinished)
|
||||
{
|
||||
try
|
||||
{
|
||||
frameFinished = CAcinerella.AcGetFrame(_Instance, _Videodecoder);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CLog.LogError("Error AcGetFrame " + _Thread.Name);
|
||||
}
|
||||
}
|
||||
if (frameFinished)
|
||||
_FrameAvailable = true;
|
||||
else
|
||||
{
|
||||
if (_Loop)
|
||||
{
|
||||
lock (_MutexSyncSignals)
|
||||
{
|
||||
_Start = 0f;
|
||||
_Gap = 0f;
|
||||
_SetTime = 0f;
|
||||
}
|
||||
_Skip();
|
||||
}
|
||||
else
|
||||
_NoMoreFrames = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void _Copy()
|
||||
{
|
||||
var videodecoder = (SACDecoder)Marshal.PtrToStructure(_Videodecoder, typeof(SACDecoder));
|
||||
if (videodecoder.Buffer == IntPtr.Zero)
|
||||
return;
|
||||
_LastDecodedTime = (float)videodecoder.Timecode;
|
||||
_Framebuffer.Put(videodecoder.Buffer, _LastDecodedTime);
|
||||
_FrameAvailable = false;
|
||||
}
|
||||
|
||||
private void _UploadNewFrame(ref CTexture frame)
|
||||
{
|
||||
lock (_MutexFramebuffer)
|
||||
{
|
||||
CFrame curFrame = _FindFrame();
|
||||
|
||||
if (curFrame != null)
|
||||
{
|
||||
CDraw.UpdateOrAddTexture(ref frame, _Width, _Height, curFrame.Data);
|
||||
|
||||
lock (_MutexSyncSignals)
|
||||
{
|
||||
_LastShownTime = curFrame.Time;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_NoMoreFrames && _Framebuffer.IsEmpty())
|
||||
Finished = true;
|
||||
}
|
||||
}
|
||||
if (!Finished && _IsSleeping)
|
||||
{
|
||||
_IsSleeping = false;
|
||||
_EvWakeUp.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private CFrame _FindFrame()
|
||||
{
|
||||
CFrame result = null;
|
||||
float now = _SetTime + _Gap;
|
||||
float maxEnd = now - _FrameDuration * 2; //Get only frames younger than 2
|
||||
CFrame frame;
|
||||
bool paused = _Paused;
|
||||
while ((frame = _Framebuffer.Get()) != null)
|
||||
{
|
||||
float frameEnd = frame.Time + _FrameDuration;
|
||||
//Don't show frames that are shown during or after now
|
||||
if (frameEnd >= now)
|
||||
break; //All following frames are after that one
|
||||
if (paused) // Don't modify anything
|
||||
break;
|
||||
_Framebuffer.SetRead();
|
||||
//Get the last(newest) possible frame and skip the rest to force in-order showing
|
||||
if (frameEnd <= maxEnd)
|
||||
continue;
|
||||
maxEnd = frameEnd;
|
||||
result = frame;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,24 +20,35 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace Vocaluxe.Lib.Video.Acinerella
|
||||
{
|
||||
class CFrame
|
||||
{
|
||||
public readonly byte[] Data;
|
||||
public float Time = -1;
|
||||
|
||||
public CFrame(int dataSize)
|
||||
{
|
||||
Data = new byte[dataSize];
|
||||
}
|
||||
}
|
||||
|
||||
class CFramebuffer
|
||||
{
|
||||
public class CFrame
|
||||
{
|
||||
private readonly CFramebuffer _Parent;
|
||||
private readonly int _Index;
|
||||
public readonly byte[] Data;
|
||||
public float Time = -1;
|
||||
|
||||
public CFrame(CFramebuffer parent, int index, int dataSize)
|
||||
{
|
||||
_Parent = parent;
|
||||
_Index = index;
|
||||
Data = new byte[dataSize];
|
||||
}
|
||||
|
||||
//Only call from reader thread
|
||||
public void SetRead()
|
||||
{
|
||||
_Parent._SetRead(_Index);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly CFrame[] _Frames;
|
||||
private readonly int _Size;
|
||||
private int _DataSize;
|
||||
private int _Last;
|
||||
private int _First;
|
||||
private int _Next;
|
||||
private bool _Initialized;
|
||||
|
||||
public int Size
|
||||
@@ -45,21 +56,24 @@ namespace Vocaluxe.Lib.Video.Acinerella
|
||||
get { return _Size; }
|
||||
}
|
||||
|
||||
// Constructs a framebuffer with max. size frames
|
||||
public CFramebuffer(int size)
|
||||
{
|
||||
_Size = size;
|
||||
_Frames = new CFrame[size];
|
||||
}
|
||||
|
||||
// Initializes the framebuffer with the size of each frame
|
||||
// MUST be called before all others
|
||||
public void Init(int dataSize)
|
||||
{
|
||||
_DataSize = dataSize;
|
||||
for (int i = 0; i < _Size; i++)
|
||||
_Frames[i] = new CFrame(dataSize);
|
||||
_Frames[i] = new CFrame(this, i, dataSize);
|
||||
_Initialized = true;
|
||||
}
|
||||
|
||||
private int _Next(int current)
|
||||
private int _GetNextIndex(int current)
|
||||
{
|
||||
current++;
|
||||
return (current < _Size) ? current : 0;
|
||||
@@ -67,7 +81,7 @@ namespace Vocaluxe.Lib.Video.Acinerella
|
||||
|
||||
public bool IsFull()
|
||||
{
|
||||
return _Next(_Last) == _First;
|
||||
return _GetNextIndex(_Last) == _First;
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
@@ -75,6 +89,7 @@ namespace Vocaluxe.Lib.Video.Acinerella
|
||||
return _First == _Last;
|
||||
}
|
||||
|
||||
//Only call from writer thread
|
||||
public bool Put(IntPtr data, float time)
|
||||
{
|
||||
if (!_Initialized || IsFull())
|
||||
@@ -82,27 +97,38 @@ namespace Vocaluxe.Lib.Video.Acinerella
|
||||
CFrame frame = _Frames[_Last];
|
||||
Marshal.Copy(data, frame.Data, 0, _DataSize);
|
||||
frame.Time = time;
|
||||
_Last = _Next(_Last);
|
||||
_Last = _GetNextIndex(_Last);
|
||||
return true;
|
||||
}
|
||||
|
||||
public CFrame Get()
|
||||
//Only call from reader thread
|
||||
public void ResetStack()
|
||||
{
|
||||
return IsEmpty() ? null : _Frames[_First];
|
||||
_Next = _First;
|
||||
}
|
||||
|
||||
public bool SetRead()
|
||||
//Pops the next frame if available
|
||||
//You have to call ResetNext before the first call to this function and call the CFrame.SetRead to free the buffers
|
||||
//Only call from reader thread
|
||||
public CFrame Pop()
|
||||
{
|
||||
if (IsEmpty())
|
||||
return false;
|
||||
_First = _Next(_First);
|
||||
return true;
|
||||
if (_Next == _Last)
|
||||
return null;
|
||||
CFrame res = _Frames[_Next];
|
||||
_Next = _GetNextIndex(_Next);
|
||||
return res;
|
||||
}
|
||||
|
||||
private void _SetRead(int index)
|
||||
{
|
||||
if ((_First < index && (index < _Next || _Next < _First)) || (_Next < _First && index < _Next))
|
||||
_First = _GetNextIndex(index);
|
||||
}
|
||||
|
||||
//Only call from reader thread
|
||||
public void Clear()
|
||||
{
|
||||
_First = 0;
|
||||
_Last = 0;
|
||||
_Next = _First = _Last;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user