restructure video threading implementation
parent
871b32d742
commit
b4021156cf
|
@ -11,10 +11,6 @@ namespace MoonWorks.Video
|
||||||
{
|
{
|
||||||
public string Filename { get; }
|
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 Width => width;
|
||||||
public int Height => height;
|
public int Height => height;
|
||||||
public double FramesPerSecond { get; set; }
|
public double FramesPerSecond { get; set; }
|
||||||
|
@ -67,23 +63,6 @@ namespace MoonWorks.Video
|
||||||
FramesPerSecond = framesPerSecond;
|
FramesPerSecond = framesPerSecond;
|
||||||
|
|
||||||
Filename = filename;
|
Filename = filename;
|
||||||
|
|
||||||
StreamA = new VideoAV1Stream(device, this);
|
|
||||||
StreamB = new VideoAV1Stream(device, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: if you call this while a VideoPlayer is playing the stream, your program will explode
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!IsDisposed)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
StreamA.Dispose();
|
|
||||||
StreamB.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Threading;
|
||||||
using MoonWorks.Graphics;
|
using MoonWorks.Graphics;
|
||||||
|
|
||||||
namespace MoonWorks.Video
|
namespace MoonWorks.Video
|
||||||
{
|
{
|
||||||
|
// Note that all public methods are async.
|
||||||
internal class VideoAV1Stream : GraphicsResource
|
internal class VideoAV1Stream : GraphicsResource
|
||||||
{
|
{
|
||||||
public IntPtr Handle => handle;
|
public IntPtr Handle => handle;
|
||||||
|
@ -21,20 +24,65 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
public bool FrameDataUpdated { get; set; }
|
public bool FrameDataUpdated { get; set; }
|
||||||
|
|
||||||
private VideoAV1 Parent;
|
private BlockingCollection<Action> Actions = new BlockingCollection<Action>();
|
||||||
|
|
||||||
public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device)
|
private bool Running = false;
|
||||||
|
|
||||||
|
Thread Thread;
|
||||||
|
|
||||||
|
public VideoAV1Stream(GraphicsDevice device) : base(device)
|
||||||
{
|
{
|
||||||
handle = IntPtr.Zero;
|
handle = IntPtr.Zero;
|
||||||
Parent = video;
|
|
||||||
|
Running = true;
|
||||||
|
|
||||||
|
Thread = new Thread(ThreadMain);
|
||||||
|
Thread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Load()
|
private void ThreadMain()
|
||||||
|
{
|
||||||
|
while (Running)
|
||||||
|
{
|
||||||
|
// block until we can take an action, then run it
|
||||||
|
var action = Actions.Take();
|
||||||
|
action.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// shutting down...
|
||||||
|
while (Actions.TryTake(out var action))
|
||||||
|
{
|
||||||
|
action.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load(string filename)
|
||||||
|
{
|
||||||
|
Actions.Add(() => LoadHelper(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
Actions.Add(ResetHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReadNextFrame()
|
||||||
|
{
|
||||||
|
Actions.Add(ReadNextFrameHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unload()
|
||||||
|
{
|
||||||
|
Actions.Add(UnloadHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadHelper(string filename)
|
||||||
{
|
{
|
||||||
if (!Loaded)
|
if (!Loaded)
|
||||||
{
|
{
|
||||||
if (Dav1dfile.df_fopen(Parent.Filename, out handle) == 0)
|
if (Dav1dfile.df_fopen(filename, out handle) == 0)
|
||||||
{
|
{
|
||||||
|
Logger.LogError("Failed to load video file: " + filename);
|
||||||
throw new Exception("Failed to load video file!");
|
throw new Exception("Failed to load video file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,29 +90,20 @@ namespace MoonWorks.Video
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Unload()
|
private void ResetHelper()
|
||||||
{
|
{
|
||||||
if (Loaded)
|
if (Loaded)
|
||||||
{
|
|
||||||
Dav1dfile.df_close(handle);
|
|
||||||
handle = IntPtr.Zero;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
{
|
||||||
Dav1dfile.df_reset(handle);
|
Dav1dfile.df_reset(handle);
|
||||||
ReadNextFrame();
|
ReadNextFrame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReadNextFrame()
|
private void ReadNextFrameHelper()
|
||||||
|
{
|
||||||
|
if (Loaded && !Ended)
|
||||||
{
|
{
|
||||||
lock (this)
|
lock (this)
|
||||||
{
|
|
||||||
if (!Ended)
|
|
||||||
{
|
{
|
||||||
if (Dav1dfile.df_readvideo(
|
if (Dav1dfile.df_readvideo(
|
||||||
handle,
|
handle,
|
||||||
|
@ -91,11 +130,26 @@ namespace MoonWorks.Video
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UnloadHelper()
|
||||||
|
{
|
||||||
|
if (Loaded)
|
||||||
|
{
|
||||||
|
Dav1dfile.df_close(handle);
|
||||||
|
handle = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
{
|
{
|
||||||
Unload();
|
Unload();
|
||||||
|
Running = false;
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
Thread.Join();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@ namespace MoonWorks.Video
|
||||||
private VideoAV1 Video = null;
|
private VideoAV1 Video = null;
|
||||||
private VideoAV1Stream CurrentStream = null;
|
private VideoAV1Stream CurrentStream = null;
|
||||||
|
|
||||||
private Task ReadNextFrameTask;
|
// "double buffering" so we can loop without a stutter
|
||||||
private Task ResetTask;
|
private VideoAV1Stream StreamA { get; }
|
||||||
private Task ResetSecondaryStreamTask;
|
private VideoAV1Stream StreamB { get; }
|
||||||
|
|
||||||
private Texture yTexture = null;
|
private Texture yTexture = null;
|
||||||
private Texture uTexture = null;
|
private Texture uTexture = null;
|
||||||
|
@ -37,6 +37,9 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
public VideoPlayer(GraphicsDevice device) : base(device)
|
public VideoPlayer(GraphicsDevice device) : base(device)
|
||||||
{
|
{
|
||||||
|
StreamA = new VideoAV1Stream(device);
|
||||||
|
StreamB = new VideoAV1Stream(device);
|
||||||
|
|
||||||
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
|
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
|
||||||
|
|
||||||
timer = new Stopwatch();
|
timer = new Stopwatch();
|
||||||
|
@ -50,7 +53,7 @@ namespace MoonWorks.Video
|
||||||
{
|
{
|
||||||
if (Video != video)
|
if (Video != video)
|
||||||
{
|
{
|
||||||
Stop();
|
Unload();
|
||||||
|
|
||||||
if (RenderTexture == null)
|
if (RenderTexture == null)
|
||||||
{
|
{
|
||||||
|
@ -154,7 +157,7 @@ namespace MoonWorks.Video
|
||||||
lastTimestamp = 0;
|
lastTimestamp = 0;
|
||||||
timeElapsed = 0;
|
timeElapsed = 0;
|
||||||
|
|
||||||
ResetDav1dStream();
|
ResetDav1dStreams();
|
||||||
|
|
||||||
State = VideoState.Stopped;
|
State = VideoState.Stopped;
|
||||||
}
|
}
|
||||||
|
@ -164,9 +167,10 @@ namespace MoonWorks.Video
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Unload()
|
public void Unload()
|
||||||
{
|
{
|
||||||
ReadNextFrameTask?.Wait();
|
if (Video == null)
|
||||||
ResetTask?.Wait();
|
{
|
||||||
ResetSecondaryStreamTask?.Wait();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
timer.Reset();
|
timer.Reset();
|
||||||
|
@ -176,12 +180,9 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
State = VideoState.Stopped;
|
State = VideoState.Stopped;
|
||||||
|
|
||||||
Video.StreamA.Unload();
|
StreamA.Unload();
|
||||||
Video.StreamB.Unload();
|
StreamB.Unload();
|
||||||
|
|
||||||
ReadNextFrameTask = null;
|
|
||||||
ResetTask = null;
|
|
||||||
ResetSecondaryStreamTask = null;
|
|
||||||
Video = null;
|
Video = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,8 +209,7 @@ namespace MoonWorks.Video
|
||||||
}
|
}
|
||||||
|
|
||||||
currentFrame = thisFrame;
|
currentFrame = thisFrame;
|
||||||
ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame);
|
CurrentStream.ReadNextFrame();
|
||||||
ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentStream.Ended)
|
if (CurrentStream.Ended)
|
||||||
|
@ -217,13 +217,12 @@ namespace MoonWorks.Video
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
timer.Reset();
|
timer.Reset();
|
||||||
|
|
||||||
ResetSecondaryStreamTask = Task.Run(CurrentStream.Reset);
|
CurrentStream.Reset();
|
||||||
ResetSecondaryStreamTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
|
|
||||||
if (Loop)
|
if (Loop)
|
||||||
{
|
{
|
||||||
// Start over on the next stream!
|
// Start over on the next stream!
|
||||||
CurrentStream = (CurrentStream == Video.StreamA) ? Video.StreamB : Video.StreamA;
|
CurrentStream = (CurrentStream == StreamA) ? StreamB : StreamA;
|
||||||
currentFrame = -1;
|
currentFrame = -1;
|
||||||
timer.Start();
|
timer.Start();
|
||||||
}
|
}
|
||||||
|
@ -236,13 +235,11 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
private void UpdateRenderTexture()
|
private void UpdateRenderTexture()
|
||||||
{
|
{
|
||||||
|
uint uOffset;
|
||||||
|
uint vOffset;
|
||||||
|
|
||||||
lock (CurrentStream)
|
lock (CurrentStream)
|
||||||
{
|
{
|
||||||
ResetTask?.Wait();
|
|
||||||
ResetTask = null;
|
|
||||||
|
|
||||||
var commandBuffer = Device.AcquireCommandBuffer();
|
|
||||||
|
|
||||||
var ySpan = new Span<byte>((void*) CurrentStream.yDataHandle, (int) CurrentStream.yDataLength);
|
var ySpan = new Span<byte>((void*) CurrentStream.yDataHandle, (int) CurrentStream.yDataLength);
|
||||||
var uSpan = new Span<byte>((void*) CurrentStream.uDataHandle, (int) CurrentStream.uvDataLength);
|
var uSpan = new Span<byte>((void*) CurrentStream.uDataHandle, (int) CurrentStream.uvDataLength);
|
||||||
var vSpan = new Span<byte>((void*) CurrentStream.vDataHandle, (int) CurrentStream.uvDataLength);
|
var vSpan = new Span<byte>((void*) CurrentStream.vDataHandle, (int) CurrentStream.uvDataLength);
|
||||||
|
@ -256,6 +253,12 @@ namespace MoonWorks.Video
|
||||||
TransferBuffer.SetData(uSpan, (uint) ySpan.Length, TransferOptions.Unsafe);
|
TransferBuffer.SetData(uSpan, (uint) ySpan.Length, TransferOptions.Unsafe);
|
||||||
TransferBuffer.SetData(vSpan, (uint) (ySpan.Length + uSpan.Length), TransferOptions.Unsafe);
|
TransferBuffer.SetData(vSpan, (uint) (ySpan.Length + uSpan.Length), TransferOptions.Unsafe);
|
||||||
|
|
||||||
|
uOffset = (uint) ySpan.Length;
|
||||||
|
vOffset = (uint) (ySpan.Length + vSpan.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandBuffer = Device.AcquireCommandBuffer();
|
||||||
|
|
||||||
commandBuffer.BeginCopyPass();
|
commandBuffer.BeginCopyPass();
|
||||||
|
|
||||||
commandBuffer.UploadToTexture(
|
commandBuffer.UploadToTexture(
|
||||||
|
@ -274,7 +277,7 @@ namespace MoonWorks.Video
|
||||||
TransferBuffer,
|
TransferBuffer,
|
||||||
uTexture,
|
uTexture,
|
||||||
new BufferImageCopy{
|
new BufferImageCopy{
|
||||||
BufferOffset = (uint) ySpan.Length,
|
BufferOffset = uOffset,
|
||||||
BufferStride = CurrentStream.uvStride,
|
BufferStride = CurrentStream.uvStride,
|
||||||
BufferImageHeight = uTexture.Height
|
BufferImageHeight = uTexture.Height
|
||||||
},
|
},
|
||||||
|
@ -286,7 +289,7 @@ namespace MoonWorks.Video
|
||||||
vTexture,
|
vTexture,
|
||||||
new BufferImageCopy
|
new BufferImageCopy
|
||||||
{
|
{
|
||||||
BufferOffset = (uint) (ySpan.Length + uSpan.Length),
|
BufferOffset = vOffset,
|
||||||
BufferStride = CurrentStream.uvStride,
|
BufferStride = CurrentStream.uvStride,
|
||||||
BufferImageHeight = vTexture.Height
|
BufferImageHeight = vTexture.Height
|
||||||
},
|
},
|
||||||
|
@ -312,7 +315,6 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
Device.Submit(commandBuffer);
|
Device.Submit(commandBuffer);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static Texture CreateRenderTexture(GraphicsDevice graphicsDevice, int width, int height)
|
private static Texture CreateRenderTexture(GraphicsDevice graphicsDevice, int width, int height)
|
||||||
{
|
{
|
||||||
|
@ -338,35 +340,19 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
private void InitializeDav1dStream()
|
private void InitializeDav1dStream()
|
||||||
{
|
{
|
||||||
ReadNextFrameTask?.Wait();
|
StreamA.Load(Video.Filename);
|
||||||
ReadNextFrameTask = null;
|
StreamB.Load(Video.Filename);
|
||||||
|
|
||||||
ResetTask?.Wait();
|
CurrentStream = StreamA;
|
||||||
ResetTask = null;
|
|
||||||
|
|
||||||
ResetSecondaryStreamTask?.Wait();
|
|
||||||
ResetSecondaryStreamTask = null;
|
|
||||||
|
|
||||||
ResetTask = Task.Run(Video.StreamA.Load);
|
|
||||||
ResetTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
ResetSecondaryStreamTask = Task.Run(Video.StreamB.Load);
|
|
||||||
ResetSecondaryStreamTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
|
|
||||||
CurrentStream = Video.StreamA;
|
|
||||||
currentFrame = -1;
|
currentFrame = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetDav1dStream()
|
private void ResetDav1dStreams()
|
||||||
{
|
{
|
||||||
ReadNextFrameTask?.Wait();
|
StreamA.Reset();
|
||||||
ReadNextFrameTask = null;
|
StreamB.Reset();
|
||||||
|
|
||||||
ResetTask = Task.Run(Video.StreamA.Reset);
|
CurrentStream = StreamA;
|
||||||
ResetTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
ResetSecondaryStreamTask = Task.Run(Video.StreamB.Reset);
|
|
||||||
ResetSecondaryStreamTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
|
|
||||||
CurrentStream = Video.StreamA;
|
|
||||||
currentFrame = -1;
|
currentFrame = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue