using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using ADOFAIRenderer.Renderer; internal static class Program { static void Assert(bool condition, string message) { if (!condition) throw new Exception(message); } static int Main(string[] args) { try { if (args.Length != 2) throw new ArgumentException("Pass ffmpeg.exe and a test output directory."); Directory.CreateDirectory(args[1]); var clock = new RenderClock(); for (int i = 0; i < 60 * 60 * 4 * 60; i++) clock.Advance(); Assert(clock.Time == 14400, "Four-hour clock drift."); Assert(Math.Abs(clock.SongPosition(1001, 1.5, 0.2, 0.03) - ((14399 - 0.03) * 1.5 - 0.2)) < 1e-9, "Pitch/offset mapping failed."); var anchored = new RenderClock(); anchored.AnchorDsp(12345.5); for (int i = 0; i < 60; i++) anchored.Advance(); Assert(anchored.DspTime == 12346.5 && anchored.Time == 1, "Audio DSP anchoring changed virtual frame time."); bool anchorRejected = false; try { anchored.AnchorDsp(0); } catch (InvalidOperationException) { anchorRejected = true; } Assert(anchorRejected, "DSP clock was allowed to reanchor during rendering."); var fast = Path.Combine(args[1], "fast.mp4"); var slow = Path.Combine(args[1], "slow.mp4"); Encode(args[0], fast, false); Encode(args[0], slow, true); var fastHash = Probe(args[0], "-v error -i \"" + fast + "\" -f framemd5 -"); var slowHash = Probe(args[0], "-v error -i \"" + slow + "\" -f framemd5 -"); Assert(fastHash == slowHash, "Different wall-clock delays changed decoded frames."); Assert(fastHash.Contains("#tb 0: 1/60") && fastHash.Contains("#dimensions 0: 1920x1080"), "Wrong frame rate or resolution."); var decoded = fastHash.Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries).Where(line => !line.StartsWith("#")).ToArray(); Assert(decoded.Length == 60, "Decoded frame count mismatch."); Assert(decoded.Select(line => line.Split(',').Last().Trim()).Distinct().Count() == 60, "Duplicate decoded frames."); for (int i = 0; i < decoded.Length; i++) { var columns = decoded[i].Split(','); Assert(long.Parse(columns[2]) == i && int.Parse(columns[3]) == 1, "Frame timestamp or duration mismatch."); } using (var encoder = new FFmpegEncoder(args[0], Path.Combine(args[1], "bad-order.mp4"))) { var frame = encoder.Rent(); frame.Index = 1; encoder.Submit(frame); bool failed = false; try { encoder.Finish(1); } catch (IOException) { failed = true; } Assert(failed, "Out-of-order frames were silently accepted."); } using (var encoder = new FFmpegEncoder(args[0], Path.Combine(args[1], "cancelled.mp4"))) { var frame = encoder.Rent(); frame.Index = 0; encoder.Submit(frame); } bool rejected = false; try { using (var encoder = new FFmpegEncoder(args[0], Path.Combine(args[1], "missing", "failure.mp4"))) { var frame = encoder.Rent(); frame.Index = 0; encoder.Submit(frame); encoder.Finish(1); }} catch (IOException) { rejected = true; } Assert(rejected, "FFmpeg nonzero exit was ignored."); var wav = Path.Combine(args[1], "tone.wav"); var muxed = Path.Combine(args[1], "with-audio.mp4"); Probe(args[0], "-v error -f lavfi -i sine=frequency=440:sample_rate=48000:duration=1 -ac 2 -c:a pcm_f32le \"" + wav + "\""); FFmpegEncoder.MuxAudio(args[0], fast, wav, muxed); var muxedVideoHash = Probe(args[0], "-v error -i \"" + muxed + "\" -map 0:v:0 -f framemd5 -"); Assert(muxedVideoHash == fastHash, "Audio mux changed video frames or timestamps."); var metadata = Probe(Path.Combine(Path.GetDirectoryName(args[0]), "ffprobe.exe"), "-v error -select_streams a:0 -show_entries stream=codec_name,sample_rate,channels,duration -of default=noprint_wrappers=1 \"" + muxed + "\""); Assert(metadata.Contains("codec_name=aac") && metadata.Contains("sample_rate=48000") && metadata.Contains("channels=2") && metadata.Contains("duration=1.000000"), "Muxed audio format/duration mismatch."); Console.WriteLine("PASS: four-hour clock, DSP anchoring, pitch/offset, 1080p60/60 frames, frame order, identical fast/slow video, failure, cancellation, AAC mux and matching A/V duration."); return 0; } catch (Exception ex) { Console.Error.WriteLine(ex); return 1; } } private static void Encode(string ffmpeg, string output, bool slow) { using (var encoder = new FFmpegEncoder(ffmpeg, output)) { for (int i = 0; i < 60; i++) { var frame = encoder.Rent(); frame.Index = i; for (int j = 0; j < frame.Bytes.Length; j += 4) { frame.Bytes[j] = (byte)(i * 4); frame.Bytes[j+1] = (byte)((j / (1920 * 4)) % 256); frame.Bytes[j+2] = (byte)(255 - i * 4); frame.Bytes[j+3] = 255; } if (slow && i % 10 == 0) Thread.Sleep(100); encoder.Submit(frame); } encoder.Finish(60); } } private static string Probe(string ffmpeg, string arguments) { using (var process = Process.Start(new ProcessStartInfo(ffmpeg, arguments) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true })) { string result = process.StandardOutput.ReadToEnd(); process.WaitForExit(); Assert(process.ExitCode == 0, "Video decode failed."); return result; } } }