Files
Vocaluxe/Vocaluxe/Lib/Video/Acinerella/CDecoder.cs
T

450 lines
13 KiB
C#

#region license
// This file is part of Vocaluxe.
//
// Vocaluxe is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vocaluxe is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
#endregion
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
{
class CDecoder
{
private IntPtr _Instance = IntPtr.Zero; // acinerella instance
private IntPtr _Videodecoder = IntPtr.Zero; // acinerella video decoder instance
private readonly Stopwatch _LoopTimer = new Stopwatch();
private readonly Thread _Thread;
private readonly Object _MutexFramebuffer = new Object();
private readonly CFramebuffer _Framebuffer;
private readonly Object _MutexSyncSignals = new Object();
private bool _FileOpened;
private float _FrameDuration; // frame time in s
private float _LastDecodedTime; // time of last decoded frame
private float _LastShownTime; // time if the last shown frame in s
private float _Time;
private float _Gap;
private float _Start;
private bool _Loop;
private float _SetTime;
private float _SetGap;
private float _SetStart;
private bool _SetSkip;
private bool _Terminated;
private bool _FrameAvailable;
private bool _NoMoreFrames;
private readonly AutoResetEvent _EvWakeUp = new AutoResetEvent(false);
private bool _IsSleeping;
private int _WaitCount;
private float _SkipTime; // = Start + VideoGap
private string _FileName; // current video file name
private int _Width;
private int _Height;
private bool _Paused;
public CDecoder()
{
_Thread = new Thread(_Execute);
_Framebuffer = new CFramebuffer(10);
}
public float Length { get; private set; }
public bool Paused
{
set
{
lock (_MutexSyncSignals)
{
_Paused = value;
if (_Paused)
_LoopTimer.Stop();
else
_LoopTimer.Start();
}
}
}
public bool Loop { private get; set; }
public bool Finished { get; private set; }
public bool Open(string fileName)
{
if (_FileOpened)
return false;
if (!File.Exists(fileName))
return false;
_FileName = fileName;
//Do this here as one may want to get the length afterwards!
if (!_DoOpen())
return false;
_FileOpened = true;
_Thread.Priority = ThreadPriority.Normal;
_Thread.Name = Path.GetFileName(fileName);
_Thread.Start();
return true;
}
public void Close()
{
_Terminated = true;
Length = 0;
}
public bool GetFrame(ref CTexture frame, float time, out float videoTime)
{
if (Loop)
{
lock (_MutexSyncSignals)
{
_SetTime += _LoopTimer.ElapsedMilliseconds / 1000f;
_LoopTimer.Restart();
}
}
else if (Math.Abs(_SetTime - time) >= 1f / 1000f) //Check 1 ms difference
{
lock (_MutexSyncSignals)
{
_SetTime = time;
}
}
else if (Math.Abs(_LastShownTime - time) < 1f / 1000 && frame != null) //Check 1 ms difference
{
videoTime = _LastShownTime;
return true;
}
_UploadNewFrame(ref frame);
videoTime = _LastShownTime;
return frame != null;
}
public bool Skip(float start, float gap)
{
lock (_MutexSyncSignals)
{
_SetStart = start;
_SetGap = gap;
_SetSkip = true;
_NoMoreFrames = false;
Finished = false;
}
return true;
}
#region Threading
private void _Skip()
{
_SkipTime = _Start + _Gap;
if (_SkipTime < 0)
_SkipTime = 0;
try
{
CAcinerella.AcSeek(_Videodecoder, -1, (Int64)(_SkipTime * 1000f));
}
catch (Exception e)
{
CLog.LogError("Error seeking video file \"" + _FileName + "\": " + e.Message);
}
_LastDecodedTime = _SkipTime;
lock (_MutexFramebuffer)
{
_Framebuffer.Clear();
}
_FrameAvailable = false;
}
private void _Execute()
{
while (!_Terminated)
{
bool skip;
lock (_MutexSyncSignals)
{
_Time = _SetTime;
skip = _SetSkip;
_SetSkip = false;
_Gap = _SetGap;
_Start = _SetStart;
_Loop = Loop;
}
if (skip)
_Skip();
if (!_FrameAvailable)
_Decode();
if (_FrameAvailable)
{
if (!_Framebuffer.IsFull())
{
_Copy();
_WaitCount = 0;
Thread.Sleep(1);
}
else if (_WaitCount > 3)
{
_IsSleeping = true;
_EvWakeUp.WaitOne();
}
else
{
Thread.Sleep((int)((_Framebuffer.Size - 2) * _FrameDuration * 1000));
_WaitCount++;
}
}
}
_Free();
}
private bool _DoOpen()
{
bool ok;
try
{
_Instance = CAcinerella.AcInit();
CAcinerella.AcOpen2(_Instance, _FileName);
var instance = (SACInstance)Marshal.PtrToStructure(_Instance, typeof(SACInstance));
Length = instance.Info.Duration / 1000f;
ok = instance.Opened;
}
catch (Exception)
{
ok = false;
}
if (!ok)
{
CLog.LogError("Error opening video file: " + _FileName);
return false;
}
int videoStreamIndex;
SACDecoder videodecoder;
try
{
_Videodecoder = CAcinerella.AcCreateVideoDecoder(_Instance);
videodecoder = (SACDecoder)Marshal.PtrToStructure(_Videodecoder, typeof(SACDecoder));
videoStreamIndex = videodecoder.StreamIndex;
}
catch (Exception)
{
CLog.LogError("Error opening video file (can't find decoder): " + _FileName);
_Free();
return false;
}
if (videoStreamIndex < 0)
{
_Free();
return false;
}
_Width = videodecoder.StreamInfo.VideoInfo.FrameWidth;
_Height = videodecoder.StreamInfo.VideoInfo.FrameHeight;
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;
}
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 " + _FileName);
}
}
if (!frameFinished)
{
try
{
frameFinished = CAcinerella.AcGetFrame(_Instance, _Videodecoder);
}
catch (Exception)
{
CLog.LogError("Error AcGetFrame " + _FileName);
}
}
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;
}
private void _Free()
{
if (_Videodecoder != IntPtr.Zero)
CAcinerella.AcFreeDecoder(_Videodecoder);
if (_Instance != IntPtr.Zero)
{
CAcinerella.AcClose(_Instance);
CAcinerella.AcFree(_Instance);
}
}
#endregion Threading
}
}