double buffered video for fast loops

pull/49/head
cosmonaut 2023-06-07 12:32:27 -07:00
parent 1954665c93
commit c70560025d
3 changed files with 125 additions and 82 deletions

View File

@ -3,10 +3,13 @@ using System.IO;
namespace MoonWorks.Video
{
public unsafe class VideoAV1 : IDisposable
public unsafe class VideoAV1
{
public IntPtr Handle => handle;
IntPtr handle;
public string Filename { get; }
// "double buffering" so we can loop without a stutter
internal VideoAV1Stream StreamA { get; }
internal VideoAV1Stream StreamB { get; }
public int Width => width;
public int Height => height;
@ -19,8 +22,6 @@ namespace MoonWorks.Video
private int height;
private Dav1dfile.PixelLayout pixelLayout;
bool IsDisposed;
public VideoAV1(string filename, double framesPerSecond)
{
if (!File.Exists(filename))
@ -28,12 +29,13 @@ namespace MoonWorks.Video
throw new ArgumentException("Video file not found!");
}
if (Dav1dfile.df_fopen(filename, out handle) == 0)
if (Dav1dfile.df_fopen(filename, out var handle) == 0)
{
throw new Exception("Failed to open video file!");
}
Dav1dfile.df_videoinfo(Handle, out width, out height, out pixelLayout);
Dav1dfile.df_videoinfo(handle, out width, out height, out pixelLayout);
Dav1dfile.df_close(handle);
if (pixelLayout == Dav1dfile.PixelLayout.I420)
{
@ -56,34 +58,11 @@ namespace MoonWorks.Video
}
FramesPerSecond = framesPerSecond;
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
// dispose managed state (managed objects)
}
Filename = filename;
// free unmanaged resources (unmanaged objects)
Dav1dfile.df_close(Handle);
IsDisposed = true;
}
}
~VideoAV1()
{
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
StreamA = new VideoAV1Stream(this);
StreamB = new VideoAV1Stream(this);
}
}
}

View File

@ -0,0 +1,91 @@
using System;
namespace MoonWorks.Video
{
internal class VideoAV1Stream
{
public IntPtr Handle => handle;
IntPtr handle;
public bool Ended => Dav1dfile.df_eos(Handle) == 1;
public IntPtr yDataHandle;
public IntPtr uDataHandle;
public IntPtr vDataHandle;
public uint yDataLength;
public uint uvDataLength;
public uint yStride;
public uint uvStride;
public bool FrameDataUpdated { get; private set; }
bool IsDisposed;
public VideoAV1Stream(VideoAV1 video)
{
if (Dav1dfile.df_fopen(video.Filename, out handle) == 0)
{
throw new Exception("Failed to open video file!");
}
Reset();
}
public void Reset()
{
lock (this)
{
Dav1dfile.df_reset(Handle);
ReadNextFrame();
}
}
public void ReadNextFrame()
{
lock (this)
{
if (Dav1dfile.df_readvideo(
Handle,
1,
out yDataHandle,
out uDataHandle,
out vDataHandle,
out yDataLength,
out uvDataLength,
out yStride,
out uvStride) == 1
) {
FrameDataUpdated = true;
}
}
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
// dispose managed state (managed objects)
}
// free unmanaged resources (unmanaged objects)
Dav1dfile.df_close(Handle);
IsDisposed = true;
}
}
~VideoAV1Stream()
{
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using MoonWorks.Audio;
using MoonWorks.Graphics;
@ -14,6 +15,7 @@ namespace MoonWorks.Video
public float PlaybackSpeed { get; set; } = 1;
private VideoAV1 Video = null;
private VideoAV1Stream CurrentStream = null;
private GraphicsDevice GraphicsDevice;
private Texture yTexture = null;
@ -21,17 +23,6 @@ namespace MoonWorks.Video
private Texture vTexture = null;
private Sampler LinearSampler;
private object readLock = new object();
private bool frameDataUpdated;
private IntPtr yDataHandle;
private IntPtr uDataHandle;
private IntPtr vDataHandle;
private uint yDataLength;
private uint uvDataLength;
private uint yStride;
private uint uvStride;
private int currentFrame;
private Stopwatch timer;
@ -152,7 +143,7 @@ namespace MoonWorks.Video
lastTimestamp = 0;
timeElapsed = 0;
Dav1dfile.df_reset(Video.Handle);
InitializeDav1dStream();
State = VideoState.Stopped;
}
@ -176,28 +167,27 @@ namespace MoonWorks.Video
int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond)));
if (thisFrame > currentFrame)
{
if (frameDataUpdated)
if (CurrentStream.FrameDataUpdated)
{
UpdateRenderTexture();
}
currentFrame = thisFrame;
System.Threading.Tasks.Task.Run(ReadNextFrame);
Task.Run(CurrentStream.ReadNextFrame);
}
bool ended = Dav1dfile.df_eos(Video.Handle) == 1;
if (ended)
if (CurrentStream.Ended)
{
timer.Stop();
timer.Reset();
Dav1dfile.df_reset(Video.Handle);
Task.Run(CurrentStream.Reset);
if (Loop)
{
// Start over!
InitializeDav1dStream();
// Start over on the next stream!
CurrentStream = (CurrentStream == Video.StreamA) ? Video.StreamB : Video.StreamA;
currentFrame = -1;
timer.Start();
}
else
@ -209,7 +199,7 @@ namespace MoonWorks.Video
private void UpdateRenderTexture()
{
lock (readLock)
lock (CurrentStream)
{
var commandBuffer = GraphicsDevice.AcquireCommandBuffer();
@ -217,13 +207,13 @@ namespace MoonWorks.Video
yTexture,
uTexture,
vTexture,
yDataHandle,
uDataHandle,
vDataHandle,
yDataLength,
uvDataLength,
yStride,
uvStride
CurrentStream.yDataHandle,
CurrentStream.uDataHandle,
CurrentStream.vDataHandle,
CurrentStream.yDataLength,
CurrentStream.uvDataLength,
CurrentStream.yStride,
CurrentStream.uvStride
);
commandBuffer.BeginRenderPass(
@ -269,28 +259,11 @@ namespace MoonWorks.Video
private void InitializeDav1dStream()
{
System.Threading.Tasks.Task.Run(ReadNextFrame);
currentFrame = -1;
}
Task.Run(Video.StreamA.Reset);
Task.Run(Video.StreamB.Reset);
private void ReadNextFrame()
{
lock (readLock)
{
if (Dav1dfile.df_readvideo(
Video.Handle,
1,
out yDataHandle,
out uDataHandle,
out vDataHandle,
out yDataLength,
out uvDataLength,
out yStride,
out uvStride) == 1
) {
frameDataUpdated = true;
}
}
CurrentStream = Video.StreamA;
currentFrame = -1;
}
protected virtual void Dispose(bool disposing)