InferenceWorker/PrefetchedFrames.cs
using System.Collections.Concurrent;
using HumanoidMocap.Inference;
namespace HumanoidMocap.Worker;
/// <summary>Decodes a video on a background thread a few frames ahead of the caller, so decoding (about
/// 18 ms per 1080p frame) overlaps the inference that consumes the frames instead of adding to it. The
/// decoder lives entirely on its own thread; frames arrive in presentation order.</summary>
public static class PrefetchedFrames
{
/// <summary>Frames ahead of the consumer. Each 1080p frame is 8 MB, 4K 33 MB.</summary>
public const int Depth=3;
/// <param name="start">Seconds to seek to first (the preceding keyframe); earlier frames may still arrive.</param>
public static IEnumerable<DecodedVideoFrame> Read(string video,CancellationToken cancellation,double start=0)
{
using var queue=new BlockingCollection<DecodedVideoFrame>(Depth);
using var stop=CancellationTokenSource.CreateLinkedTokenSource(cancellation);
Exception? failure=null;
var producer=new Thread(()=>
{
try
{
using var decoder=new WindowsVideoDecoder(video);DecodedVideoFrame? frame;
if(start>1)decoder.Seek(start-1);
while((frame=decoder.Read(stop.Token)) is not null)queue.Add(frame,stop.Token);
}
catch(OperationCanceledException){}
catch(Exception error){failure=error;}
finally{queue.CompleteAdding();}
}){IsBackground=true,Name="Video prefetch"};
producer.Start();
try
{
foreach(var frame in queue.GetConsumingEnumerable(cancellation))yield return frame;
if(failure is not null)System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(failure).Throw();
}
finally{stop.Cancel();producer.Join();}
}
}