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 namespace MoonWorks.Video
{ {
public unsafe class VideoAV1 : IDisposable public unsafe class VideoAV1
{ {
public IntPtr Handle => handle; public string Filename { get; }
IntPtr handle;
// "double buffering" so we can loop without a stutter
internal VideoAV1Stream StreamA { get; }
internal VideoAV1Stream StreamB { get; }
public int Width => width; public int Width => width;
public int Height => height; public int Height => height;
@ -19,8 +22,6 @@ namespace MoonWorks.Video
private int height; private int height;
private Dav1dfile.PixelLayout pixelLayout; private Dav1dfile.PixelLayout pixelLayout;
bool IsDisposed;
public VideoAV1(string filename, double framesPerSecond) public VideoAV1(string filename, double framesPerSecond)
{ {
if (!File.Exists(filename)) if (!File.Exists(filename))
@ -28,12 +29,13 @@ namespace MoonWorks.Video
throw new ArgumentException("Video file not found!"); 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!"); 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) if (pixelLayout == Dav1dfile.PixelLayout.I420)
{ {
@ -56,34 +58,11 @@ namespace MoonWorks.Video
} }
FramesPerSecond = framesPerSecond; FramesPerSecond = framesPerSecond;
}
protected virtual void Dispose(bool disposing) Filename = filename;
{
if (!IsDisposed)
{
if (disposing)
{
// dispose managed state (managed objects)
}
// free unmanaged resources (unmanaged objects) StreamA = new VideoAV1Stream(this);
Dav1dfile.df_close(Handle); StreamB = new VideoAV1Stream(this);
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);
} }
} }
} }

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