tests/test_ffmpeg_video.cs
using System.Diagnostics;
using HumanoidMocapX.EditorTools;
using HumanoidMocapX.EditorTools.Video;
static class FfmpegVideoTests
{
internal static async Task Run()
{
// Windows decodes through Media Foundation; FFmpeg is the decoder elsewhere.
if (OperatingSystem.IsWindows() || !FfmpegVideoDecoder.IsInstalled) return;
var directory = Path.Combine(Path.GetTempPath(), "mocap-ffmpeg-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
try
{
// Two seconds of FFmpeg's test pattern: 60 frames at 30 fps, 64x48, H.264 4:2:0.
var clip = Path.Combine(directory, "clip.mp4");
var encode = Process.Start(new ProcessStartInfo("ffmpeg", ["-v", "error", "-f", "lavfi", "-i", "testsrc=size=64x48:rate=30",
"-t", "2", "-c:v", "libx264", "-pix_fmt", "yuv420p", clip]) { RedirectStandardError = true })!;
await encode.WaitForExitAsync();
MotionTests.Check(encode.ExitCode == 0, "ffmpeg could not encode the test clip: " + await encode.StandardError.ReadToEndAsync());
using (var decoder = new FfmpegVideoDecoder(clip))
{
MotionTests.Near(2, decoder.Duration, .05);
var times = new List<double>();
while (decoder.Read(default) is { } frame)
{
MotionTests.Equal(64, frame.Width); MotionTests.Equal(48, frame.Height);
MotionTests.Equal(64 * 48 * 4, frame.Rgba.Length);
times.Add(frame.Time);
}
MotionTests.Equal(60, times.Count); MotionTests.Near(0, times[0], 1e-6);
for (var i = 1; i < times.Count; i++) MotionTests.Near(i / 30d, times[i], 1e-6);
// A seek decodes again from at or before the target.
decoder.Seek(1.5);
var resumed = decoder.Read(default);
MotionTests.Check(resumed is not null && resumed.Time <= 1.5 + 1e-6, "Seek skipped past its target");
}
using (var decoder = IVideoDecoder.Open(clip))
{
decoder.Seek(.5);
using var frames = await VideoFrames.CreateAsync(decoder.Read, decoder.Duration, .5, 1.5, directory, null, default);
MotionTests.Equal(30, frames.Count); MotionTests.Equal(64, frames.Width); MotionTests.Equal(48, frames.Height);
}
MotionTests.Throws<FileNotFoundException>(() => new FfmpegVideoDecoder(Path.Combine(directory, "missing.mp4")));
var text = Path.Combine(directory, "notes.mp4"); await File.WriteAllTextAsync(text, "not a video");
MotionTests.Throws<InvalidDataException>(() => new FfmpegVideoDecoder(text));
}
finally { Directory.Delete(directory, true); }
Console.WriteLine("PASS: FFmpeg decoding of H.264 frames, timestamps, seeks, the 30 fps timeline, missing and invalid files");
}
}