diff --git a/Cargo.lock b/Cargo.lock index e3ab737..7178797 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,28 @@ dependencies = [ "memchr", ] +[[package]] +name = "alsa" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d" +dependencies = [ + "alsa-sys", + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad7569085a265dd3f607ebecce7458eaab2132a84393534c95b18dcbc3f31e04" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "annotate-snippets" version = "0.11.5" @@ -99,6 +121,39 @@ dependencies = [ "system-deps 6.2.2", ] +[[package]] +name = "audio-codec-algorithms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1254ebf6529f3763c491acfb5ab6e960809e3c75a38584cc664f8c5667fb7107" + +[[package]] +name = "audioadapter" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1292ef9edf681b7426ed089004b021a42896492f58bd0c909ad44e5faec9ac4" + +[[package]] +name = "audioadapter-buffers" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46289a81a3bfa26d0f8b2415f8ca9decde9a66308a150568af5c102381b3c8ce" +dependencies = [ + "audioadapter", + "audioadapter-sample", + "num-traits", +] + +[[package]] +name = "audioadapter-sample" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5258faecf4edbe35ec483bda9b1f7438e6b391891819269c6b5461aeaa0e8d7d" +dependencies = [ + "audio-codec-algorithms", + "num-traits", +] + [[package]] name = "autocfg" version = "1.5.1" @@ -133,6 +188,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -232,12 +296,66 @@ dependencies = [ "libloading", ] +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "cookie-factory" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +[[package]] +name = "coreaudio-rs" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58" +dependencies = [ + "bitflags", + "libc", + "objc2-audio-toolbox", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "cpal" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f02e8d0327b42d3e2e4ab2119af397344eb9fc54a34bf0ddeaa1277af8681f1" +dependencies = [ + "alsa", + "block2", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "objc2", + "objc2-audio-toolbox", + "objc2-avf-audio", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", + "web-sys", + "windows", + "windows-core", +] + [[package]] name = "crossbeam-channel" version = "0.5.16" @@ -253,6 +371,12 @@ version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + [[package]] name = "dirs" version = "6.0.0" @@ -880,6 +1004,64 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.20", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + [[package]] name = "js-sys" version = "0.3.104" @@ -960,6 +1142,18 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + [[package]] name = "memchr" version = "2.8.3" @@ -1015,6 +1209,35 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + [[package]] name = "nom" version = "7.1.3" @@ -1044,6 +1267,26 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +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 2.0.119", +] + [[package]] name = "num-integer" version = "0.1.47" @@ -1084,6 +1327,28 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "objc2" version = "0.6.4" @@ -1104,6 +1369,55 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-audio-toolbox" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08" +dependencies = [ + "bitflags", + "libc", + "objc2", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-avf-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" +dependencies = [ + "bitflags", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" +dependencies = [ + "dispatch2", + "objc2", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-audio-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "objc2-core-foundation" version = "0.3.2" @@ -1111,7 +1425,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags", + "block2", "dispatch2", + "libc", "objc2", ] @@ -1128,6 +1444,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags", + "block2", + "libc", "objc2", "objc2-core-foundation", ] @@ -1237,6 +1555,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "primal-check" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" +dependencies = [ + "num-integer", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1323,6 +1650,15 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "realfft" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f821338fddb99d089116342c46e9f1fbf3828dba077674613e734e01d6ea8677" +dependencies = [ + "rustfft", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -1374,6 +1710,22 @@ dependencies = [ "portable-atomic-util", ] +[[package]] +name = "rubato" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cb1ffaf8738df50aab642a7f6465df81c6ba9e2818268053487165298114be" +dependencies = [ + "audioadapter", + "audioadapter-buffers", + "num-complex", + "num-integer", + "num-traits", + "realfft", + "visibility", + "windowfunctions", +] + [[package]] name = "rustc-hash" version = "2.1.3" @@ -1389,6 +1741,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustfft" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", +] + [[package]] name = "rustix" version = "1.1.4" @@ -1408,6 +1774,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "semver" version = "1.0.28" @@ -1508,6 +1883,22 @@ dependencies = [ "libc", ] +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -1530,6 +1921,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "syn" version = "1.0.109" @@ -1821,6 +2218,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "uds_windows" version = "1.2.1" @@ -1879,11 +2286,23 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "vrec" version = "0.1.0" dependencies = [ "ashpd", + "cpal", "crossbeam-channel", "dirs", "ffmpeg-next", @@ -1898,12 +2317,23 @@ dependencies = [ "mp4", "pipewire", "ringbuf", + "rubato", "serde", "serde_json", "tokio", "xcb", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1955,6 +2385,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1971,18 +2411,131 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windowfunctions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90628d739333b7c5d2ee0b70210b97b8cddc38440c682c96fd9e2c24c2db5f3a" +dependencies = [ + "num-traits", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -2017,6 +2570,15 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 19ee7b8..de96316 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,5 @@ serde_json = "1.0.151" tokio = { version = "1.53.1", features = ["rt", "macros", "rt-multi-thread"] } xcb = { version = "1.7.1", features = ["shm", "xfixes"] } dirs = "6.0.0" +cpal = "0.18.2" +rubato = "5.0.0" diff --git a/src/bin/vrec-ui.rs b/src/bin/vrec-ui.rs index 168c470..1bde75d 100644 --- a/src/bin/vrec-ui.rs +++ b/src/bin/vrec-ui.rs @@ -38,8 +38,11 @@ fn main() -> Result<(), Box> { } let manager = GlobalHotKeyManager::new().unwrap(); - let save_hotkey = HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::KeyR); - let menu_hotkey = HotKey::new(Some(Modifiers::ALT), Code::KeyZ); + let config = vrec::config::VrecConfig::load(); + let save_hotkey = vrec::hotkey::parse_hotkey(&config.save_hotkey) + .unwrap_or_else(|| HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::KeyR)); + let menu_hotkey = vrec::hotkey::parse_hotkey(&config.menu_hotkey) + .unwrap_or_else(|| HotKey::new(Some(Modifiers::ALT), Code::KeyZ)); if let Err(e) = manager.register(save_hotkey) { eprintln!("Failed to register global hotkey! Wayland compositor might be blocking it: {}", e); diff --git a/src/capture/mod.rs b/src/capture/mod.rs index f4bb4ee..39c47d6 100644 --- a/src/capture/mod.rs +++ b/src/capture/mod.rs @@ -35,3 +35,4 @@ pub trait FrameSource: Send { fn next_frame(&mut self) -> Result>; } pub mod mock; +pub mod audio; diff --git a/src/encoder.rs b/src/encoder.rs index b3913ef..0985fa7 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -220,3 +220,141 @@ impl Drop for VaapiEncoder { } } } + +pub struct AudioEncoder { + codec_ctx: *mut AVCodecContext, + swr_ctx: *mut SwrContext, + next_pts: i64, + frame_size: i32, + channels: i32, + fifo: *mut AVAudioFifo, +} + +impl AudioEncoder { + pub fn new(sample_rate: i32, channels: i32) -> Result { + unsafe { + let codec = avcodec_find_encoder(AVCodecID::AV_CODEC_ID_AAC); + if codec.is_null() { + return Err("AAC encoder not found".into()); + } + + let codec_ctx = avcodec_alloc_context3(codec); + (*codec_ctx).sample_rate = sample_rate; + (*codec_ctx).ch_layout.nb_channels = channels; + // AAC requires FLTP (planar float) + (*codec_ctx).sample_fmt = AVSampleFormat::AV_SAMPLE_FMT_FLTP; + (*codec_ctx).bit_rate = 192_000; + // For older ffmpeg compatibility: + (*codec_ctx).channel_layout = if channels == 2 { AV_CH_LAYOUT_STEREO } else { AV_CH_LAYOUT_MONO } as u64; + + let ret = avcodec_open2(codec_ctx, codec, std::ptr::null_mut()); + if ret < 0 { + return Err("Failed to open AAC encoder".into()); + } + + // Create SwrContext to convert from interleaved F32 (AV_SAMPLE_FMT_FLT) to planar F32 (AV_SAMPLE_FMT_FLTP) + let mut swr_ctx = swr_alloc(); + av_opt_set_int(swr_ctx as _, c"in_channel_layout".as_ptr(), (*codec_ctx).channel_layout as i64, 0); + av_opt_set_int(swr_ctx as _, c"in_sample_rate".as_ptr(), sample_rate as i64, 0); + av_opt_set_sample_fmt(swr_ctx as _, c"in_sample_fmt".as_ptr(), AVSampleFormat::AV_SAMPLE_FMT_FLT, 0); + + av_opt_set_int(swr_ctx as _, c"out_channel_layout".as_ptr(), (*codec_ctx).channel_layout as i64, 0); + av_opt_set_int(swr_ctx as _, c"out_sample_rate".as_ptr(), sample_rate as i64, 0); + av_opt_set_sample_fmt(swr_ctx as _, c"out_sample_fmt".as_ptr(), AVSampleFormat::AV_SAMPLE_FMT_FLTP, 0); + + swr_init(swr_ctx); + + let frame_size = (*codec_ctx).frame_size; + let fifo = av_audio_fifo_alloc(AVSampleFormat::AV_SAMPLE_FMT_FLTP, channels, 1024 * 10); + + Ok(Self { + codec_ctx, + swr_ctx, + next_pts: 0, + frame_size, + channels, + fifo, + }) + } + } + + pub fn codec_ctx(&self) -> *mut AVCodecContext { + self.codec_ctx + } + + pub fn encode_pcm(&mut self, data: &[f32]) -> Result, String> { + let mut packets = Vec::new(); + unsafe { + let nb_samples = data.len() as i32 / self.channels; + + // Allocate input frame (interleaved FLT) + let mut in_frame = av_frame_alloc(); + (*in_frame).nb_samples = nb_samples; + (*in_frame).format = AVSampleFormat::AV_SAMPLE_FMT_FLT as i32; + (*in_frame).channel_layout = (*self.codec_ctx).channel_layout; + (*in_frame).sample_rate = (*self.codec_ctx).sample_rate; + av_frame_get_buffer(in_frame, 0); + + std::slice::from_raw_parts_mut((*in_frame).data[0], data.len() * 4) + .copy_from_slice(std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4)); + + // Allocate output frame (planar FLTP) + let mut out_frame = av_frame_alloc(); + (*out_frame).nb_samples = nb_samples; + (*out_frame).format = AVSampleFormat::AV_SAMPLE_FMT_FLTP as i32; + (*out_frame).channel_layout = (*self.codec_ctx).channel_layout; + (*out_frame).sample_rate = (*self.codec_ctx).sample_rate; + av_frame_get_buffer(out_frame, 0); + + swr_convert( + self.swr_ctx, + (*out_frame).data.as_mut_ptr(), + nb_samples, + (*in_frame).data.as_ptr() as *const *const u8, + nb_samples + ); + + av_audio_fifo_write(self.fifo, (*out_frame).data.as_mut_ptr() as *mut *mut std::ffi::c_void, nb_samples); + + av_frame_free(&mut in_frame); + av_frame_free(&mut out_frame); + + // Read exactly frame_size chunks from FIFO and encode + while av_audio_fifo_size(self.fifo) >= self.frame_size { + let mut enc_frame = av_frame_alloc(); + (*enc_frame).nb_samples = self.frame_size; + (*enc_frame).format = AVSampleFormat::AV_SAMPLE_FMT_FLTP as i32; + (*enc_frame).channel_layout = (*self.codec_ctx).channel_layout; + (*enc_frame).sample_rate = (*self.codec_ctx).sample_rate; + av_frame_get_buffer(enc_frame, 0); + + av_audio_fifo_read(self.fifo, (*enc_frame).data.as_mut_ptr() as *mut *mut std::ffi::c_void, self.frame_size); + + (*enc_frame).pts = self.next_pts; + self.next_pts += self.frame_size as i64; + + if avcodec_send_frame(self.codec_ctx, enc_frame) >= 0 { + let mut pkt = av_packet_alloc(); + while avcodec_receive_packet(self.codec_ctx, pkt) >= 0 { + let new_pkt = av_packet_alloc(); + av_packet_move_ref(new_pkt, pkt); + packets.push(crate::ring::Packet::new(new_pkt)); + } + av_packet_free(&mut pkt); + } + av_frame_free(&mut enc_frame); + } + } + Ok(packets) + } +} + +impl Drop for AudioEncoder { + fn drop(&mut self) { + unsafe { + av_audio_fifo_free(self.fifo); + swr_free(&mut self.swr_ctx); + avcodec_free_context(&mut self.codec_ctx); + } + } +} diff --git a/src/hotkey.rs b/src/hotkey.rs index 12fbba5..78b97e7 100644 --- a/src/hotkey.rs +++ b/src/hotkey.rs @@ -1,19 +1,53 @@ -use global_hotkey::{GlobalHotKeyManager, hotkey::{HotKey, Modifiers, Code}, GlobalHotKeyEvent}; -use crossbeam_channel::Sender; +use global_hotkey::hotkey::{HotKey, Modifiers, Code}; -pub fn run_hotkey_listener(trigger_tx: Sender<()>) -> Result<(), Box> { - let manager = GlobalHotKeyManager::new()?; - let hotkey = HotKey::new(Some(Modifiers::ALT), Code::KeyZ); - manager.register(hotkey)?; - - println!("Registered global hotkey Ctrl+Shift+R to trigger replay save."); - - let receiver = GlobalHotKeyEvent::receiver(); - loop { - if let Ok(event) = receiver.recv() - && event.id == hotkey.id() && event.state == global_hotkey::HotKeyState::Pressed { - println!("Hotkey triggered! Saving replay..."); - let _ = trigger_tx.send(()); +pub fn parse_hotkey(s: &str) -> Option { + let mut mods = Modifiers::empty(); + let mut code = None; + for part in s.split('+') { + let p = part.trim().to_lowercase(); + match p.as_str() { + "ctrl" | "control" => mods |= Modifiers::CONTROL, + "shift" => mods |= Modifiers::SHIFT, + "alt" => mods |= Modifiers::ALT, + "meta" | "super" | "windows" | "cmd" => mods |= Modifiers::META, + _ => { + // simple mapping + code = match p.as_str() { + "a" => Some(Code::KeyA), + "b" => Some(Code::KeyB), + "c" => Some(Code::KeyC), + "d" => Some(Code::KeyD), + "e" => Some(Code::KeyE), + "f" => Some(Code::KeyF), + "g" => Some(Code::KeyG), + "h" => Some(Code::KeyH), + "i" => Some(Code::KeyI), + "j" => Some(Code::KeyJ), + "k" => Some(Code::KeyK), + "l" => Some(Code::KeyL), + "m" => Some(Code::KeyM), + "n" => Some(Code::KeyN), + "o" => Some(Code::KeyO), + "p" => Some(Code::KeyP), + "q" => Some(Code::KeyQ), + "r" => Some(Code::KeyR), + "s" => Some(Code::KeyS), + "t" => Some(Code::KeyT), + "u" => Some(Code::KeyU), + "v" => Some(Code::KeyV), + "w" => Some(Code::KeyW), + "x" => Some(Code::KeyX), + "y" => Some(Code::KeyY), + "z" => Some(Code::KeyZ), + _ => None, + }; } + } + } + + if let Some(c) = code { + Some(HotKey::new(Some(mods), c)) + } else { + None } }