From 8efb9c031e04804c29b77092d5f01783466d5db9 Mon Sep 17 00:00:00 2001 From: zeefaad Date: Tue, 16 Jun 2026 10:26:16 +0200 Subject: [PATCH] sync + image --- .gitignore | 1 + Cargo.lock | 520 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/frame.rs | 45 ++++ src/line_assembler.rs | 58 +++++ src/main.rs | 116 ++++++---- src/pipeline.rs | 13 ++ src/sync.rs | 86 +++++++ 8 files changed, 795 insertions(+), 45 deletions(-) create mode 100644 src/frame.rs create mode 100644 src/line_assembler.rs create mode 100644 src/sync.rs diff --git a/.gitignore b/.gitignore index e168e91..2960132 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target test.iq +test.py diff --git a/Cargo.lock b/Cargo.lock index 0a2bd96..675de15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,6 +150,24 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -163,6 +181,7 @@ dependencies = [ "eframe", "egui", "egui_plot", + "image", "indicatif", "num-complex", ] @@ -207,6 +226,12 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "arboard" version = "3.6.1" @@ -227,6 +252,17 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -245,6 +281,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "ash" version = "0.38.0+1.3.281" @@ -434,6 +479,49 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.18", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] + [[package]] name = "bit-set" version = "0.9.1" @@ -449,6 +537,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -461,6 +555,15 @@ version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + [[package]] name = "block2" version = "0.5.1" @@ -492,6 +595,12 @@ dependencies = [ "piper", ] +[[package]] +name = "built" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" + [[package]] name = "bumpalo" version = "3.20.3" @@ -643,6 +752,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "combine" version = "4.6.7" @@ -733,6 +848,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -942,6 +1076,12 @@ dependencies = [ "emath", ] +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + [[package]] name = "emath" version = "0.34.3" @@ -1012,6 +1152,26 @@ version = "0.34.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8970033a4282a7bcf899b38b5ed3a58b732fe093d03785d58648515d8d309da" +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1064,6 +1224,21 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -1257,6 +1432,16 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gl_generator" version = "0.14.0" @@ -1559,12 +1744,38 @@ checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ "bytemuck", "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", "moxcms", "num-traits", "png", + "qoi", + "ravif", + "rayon", + "rgb", "tiff", + "zune-core", + "zune-jpeg", ] +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2" + [[package]] name = "indexmap" version = "2.14.0" @@ -1590,6 +1801,26 @@ dependencies = [ "web-time", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -1710,12 +1941,28 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libloading" version = "0.8.9" @@ -1789,6 +2036,25 @@ version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.8.2" @@ -1889,12 +2155,52 @@ dependencies = [ "jni-sys 0.3.1", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + [[package]] name = "nohash-hasher" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -1904,6 +2210,37 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2318,6 +2655,18 @@ dependencies = [ "windows-link", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "peniko" version = "0.6.1" @@ -2495,6 +2844,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "presser" version = "0.3.1" @@ -2534,6 +2892,19 @@ name = "profiling" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn", +] [[package]] name = "pxfm" @@ -2541,6 +2912,15 @@ version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quick-error" version = "2.0.1" @@ -2578,12 +2958,91 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "range-alloc" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "thiserror 2.0.18", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -2602,6 +3061,26 @@ dependencies = [ "objc2-quartz-core 0.3.2", ] +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "read-fonts" version = "0.37.0" @@ -2645,6 +3124,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2830,6 +3315,15 @@ dependencies = [ "simdutf8", ] +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "simdutf8" version = "0.1.5" @@ -3252,6 +3746,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "vello_common" version = "0.0.6" @@ -4264,6 +4769,12 @@ version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + [[package]] name = "yoke" version = "0.8.3" @@ -4470,6 +4981,15 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + [[package]] name = "zune-jpeg" version = "0.5.15" diff --git a/Cargo.toml b/Cargo.toml index 4eab2cb..67bf57f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,6 @@ edition = "2024" eframe = "0.34.3" egui = "0.34.3" egui_plot = "0.35.0" +image = "0.25.10" indicatif = "0.18.4" num-complex = "0.4.6" diff --git a/src/frame.rs b/src/frame.rs new file mode 100644 index 0000000..c26b58a --- /dev/null +++ b/src/frame.rs @@ -0,0 +1,45 @@ +use image::{GrayImage, Luma}; +use std::fs; +use std::path::Path; + +use crate::line_assembler::VideoFrame; + +// PAL +const WIDTH: u32 = 1280; +const HEIGHT: u32 = 288; + +pub fn save_frames(frames: &[VideoFrame], output_dir: &str, sig_min: f32, sig_max: f32) { + fs::create_dir_all(output_dir).expect("Could not create output directory"); + + let range = (sig_max - sig_min).max(1e-6); + let mut saved = 0; + + for (i, frame) in frames.iter().enumerate() { + if frame.lines.is_empty() { + continue; + } + + let mut img = GrayImage::new(WIDTH, HEIGHT); + + for y in 0..HEIGHT as usize { + // If the frame has fewer lines than HEIGHT, fill the rest with black + let line = frame.lines.get(y); + + for x in 0..WIDTH as usize { + let s = line + .and_then(|l| l.samples.get(x)) + .copied() + .unwrap_or(sig_min); + + let luma = ((s - sig_min) / range * 255.0).clamp(0.0, 255.0) as u8; + img.put_pixel(x as u32, y as u32, Luma([luma])); + } + } + + let path = Path::new(output_dir).join(format!("frame_{:04}.png", i)); + img.save(&path).expect("Could not save image"); + saved += 1; + } + + println!("{saved} image(s) saved to '{output_dir}/'"); +} diff --git a/src/line_assembler.rs b/src/line_assembler.rs new file mode 100644 index 0000000..2c61eb5 --- /dev/null +++ b/src/line_assembler.rs @@ -0,0 +1,58 @@ +use crate::{fm_demod::Sample, sync::SyncEvent}; + +pub struct VideoLine { + pub samples: Vec, +} + +pub struct VideoFrame { + pub lines: Vec, +} + +pub struct LineAssembler { + pub inner: I, + pub current_line: Vec, + pub current_frame: Vec, +} + +impl LineAssembler { + pub fn new(inner: I) -> Self { + Self { + inner, + current_line: Vec::new(), + current_frame: Vec::new(), + } + } +} + +impl Iterator for LineAssembler +where + I: Iterator>, +{ + type Item = Result; + + fn next(&mut self) -> Option { + loop { + match self.inner.next()? { + Ok(SyncEvent::VideoSample(s)) => { + self.current_line.push(s); + } + Ok(SyncEvent::HSync) => { + // Save the finished line + let line = VideoLine { + samples: std::mem::take(&mut self.current_line), + }; + + self.current_frame.push(line); + } + Ok(SyncEvent::VSync) => { + // Frame finished + let frame = VideoFrame { + lines: std::mem::take(&mut self.current_frame), + }; + return Some(Ok(frame)); + } + Err(e) => return Some(Err(e)), + } + } + } +} diff --git a/src/main.rs b/src/main.rs index ed5049f..dbca8cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ +use crate::fm_demod::Sample; use crate::iq_reader::FileSource; -use crate::pipeline::{AfterDemodPiplineExt, DspPipelineExt}; +use crate::pipeline::{AfterDemodPiplineExt, DspPipelineExt, SyncPipelineExt}; use egui_plot::{Line, Plot}; use indicatif::ProgressIterator; @@ -7,59 +8,84 @@ mod agc; mod dc_blocker; mod fir; mod fm_demod; +mod frame; mod iq_reader; +mod line_assembler; mod low_pass; mod pipeline; +mod sync; mod utils; -fn main() -> eframe::Result { - let source = FileSource::new("test.iq", 32769).unwrap(); - let taps = [0.5; 64]; +const SAMPLE_RATE: f32 = 20_000_000.0; +const SYNC_THRESHOLD: f32 = -0.3; +const LOWPASS_ALPHA: f32 = 0.6; +// fn main() -> eframe::Result { +fn main() { + let source = FileSource::new("test.iq", 32769).unwrap(); let samples: Vec = source - .progress() - .agc(20_000_000.0, 1.0, 0.001, 100.0) - //.fir::<64>(taps, 4) + .skip(10_000) + .take(100_000) + .agc(SAMPLE_RATE, 1.0, 0.001, 100.0) .demodulate() - .dc_block(0.995) - .lowpass(0.75) + .dc_block(0.997) + .lowpass(LOWPASS_ALPHA) .filter_map(|r| r.ok()) .collect(); - eframe::run_ui_native("DSP", Default::default(), move |ui, _frame| { - egui::CentralPanel::default().show(ui, |ui| { - Plot::new("plot").show(ui, |plot_ui| { - plot_ui.line(Line::new( - "Signal", - samples - .iter() - .take(10000) - .enumerate() - .map(|(i, x)| [i as f64, *x as f64]) - .collect::>(), - )) - }) - }); - }) -} + let min = samples.iter().cloned().fold(f32::INFINITY, f32::min); + let max = samples.iter().cloned().fold(f32::NEG_INFINITY, f32::max); + let mean: f32 = samples.iter().sum::() / samples.len() as f32; -// fn main() -> Result<(), Box> { -// let source = FileSource::new("test.iq", 32769)?; -// -// // Fir coefs -// let taps = [0.5; 64]; -// -// let pipeline = source -// .agc(20_000_000.0, 1.0, 0.001, 100.0) -// .fir::<64>(taps, 4) -// .demodulate() -// .dc_block(0.995) -// .lowpass(0.75); -// -// for s_r in pipeline { -// let s = s_r?; -// println!("phase : {}", s); -// } -// -// Ok(()) -// } + println!("min={:.3} max={:.3} mean={:.3}", min, max, mean); + + let source = FileSource::new("test.iq", 32769).unwrap(); + + // let taps = [0.5; 64]; + + let frames: Vec<_> = source + .take(50_000_000) + .progress() + .agc(SAMPLE_RATE, 1.0, 0.001, 100.0) + .demodulate() + .dc_block(0.997) + .lowpass(LOWPASS_ALPHA) + .sync_separate(SAMPLE_RATE, SYNC_THRESHOLD) + .assemble_frames() + .filter_map(|r| r.ok()) + .collect(); + + println!("{} frames decoded", frames.len()); + + frame::save_frames(&frames, "frames", min, max); + + // let source2 = FileSource::new("test.iq", 32769).unwrap(); + // + // let samples: Vec = source2 + // .skip(10_000) + // .take(10_000) + // .progress() + // .agc(20_000_000.0, 1.0, 0.001, 100.0) + // // .fir::<64>(taps, 4) + // .demodulate() + // .dc_block(0.997) + // .lowpass(0.75) + // .filter_map(|r| r.ok()) + // .collect(); + // + // eframe::run_ui_native("DSP", Default::default(), move |ui, _frame| { + // egui::CentralPanel::default().show(ui, |ui| { + // Plot::new("plot").show(ui, |plot_ui| { + // plot_ui.line(Line::new( + // "Signal", + // samples + // .iter() + // .take(10000) + // .enumerate() + // .map(|(i, x)| [i as f64, *x as f64]) + // .collect::>(), + // )) + // }) + // }); + // }) +} diff --git a/src/pipeline.rs b/src/pipeline.rs index 1a94b98..8946ca3 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -3,7 +3,9 @@ use crate::dc_blocker::DcBlocker; use crate::fir::Fir; use crate::fm_demod::{FmDemod, Sample}; use crate::iq_reader::IqSample; +use crate::line_assembler::LineAssembler; use crate::low_pass::LowPass; +use crate::sync::{SyncEvent, SyncSeparator}; pub trait DspPipelineExt: Iterator> + Sized { fn agc(self, sample_rate: f32, target_power: f32, min_gain: f32, max_gain: f32) -> Agc { @@ -29,6 +31,17 @@ pub trait AfterDemodPiplineExt: Iterator> + Sized { fn lowpass(self, alpha: f32) -> LowPass { LowPass::new(self, alpha) } + + fn sync_separate(self, sample_rate: f32, sync_threshold: f32) -> SyncSeparator { + SyncSeparator::new(self, sample_rate, sync_threshold) + } } impl AfterDemodPiplineExt for I where I: Iterator> {} + +pub trait SyncPipelineExt: Iterator> + Sized { + fn assemble_frames(self) -> LineAssembler { + LineAssembler::new(self) + } +} +impl SyncPipelineExt for I where I: Iterator> {} diff --git a/src/sync.rs b/src/sync.rs new file mode 100644 index 0000000..c5dfbec --- /dev/null +++ b/src/sync.rs @@ -0,0 +1,86 @@ +use crate::fm_demod::Sample; + +pub enum SyncState { + Active, + InPulse { count: usize }, +} + +pub enum SyncEvent { + HSync, + VSync, + VideoSample(Sample), +} + +pub struct SyncSeparator { + pub inner: I, + pub state: SyncState, + // Threshold for considering a pulse + pub sync_threshold: f32, + pub hsync_min: usize, + pub hsync_max: usize, + pub vsync_min: usize, +} + +impl SyncSeparator { + pub fn new(inner: I, sample_rate: f32, sync_threshold: f32) -> Self { + // µs -> samples + let us = |micros: f32| (micros * sample_rate / 1_000_000.0) as usize; + + Self { + inner, + state: SyncState::Active, + sync_threshold, + hsync_min: us(3.5), + hsync_max: us(6.0), + vsync_min: us(25.0), + } + } + + pub fn process_sample(&mut self, sample: Sample) -> Option { + match self.state { + SyncState::Active => { + if sample < self.sync_threshold { + self.state = SyncState::InPulse { count: 1 }; + None + } else { + Some(SyncEvent::VideoSample(sample)) + } + } + SyncState::InPulse { ref mut count } => { + if sample < self.sync_threshold { + *count += 1; + None + } else { + let pulse_len = *count; + self.state = SyncState::Active; + if pulse_len >= self.vsync_min { + Some(SyncEvent::VSync) + } else if pulse_len >= self.hsync_min && pulse_len <= self.hsync_max { + Some(SyncEvent::HSync) + } else { + None + } + } + } + } + } +} + +impl Iterator for SyncSeparator +where + I: Iterator>, +{ + type Item = Result; + fn next(&mut self) -> Option { + loop { + match self.inner.next()? { + Ok(sample) => { + if let Some(event) = self.process_sample(sample) { + return Some(Ok(event)); + } + } + Err(e) => return Some(Err(e)), + } + } + } +}