Merge pull request #8 from recurse-chat/fix-linux-audio-and-crypto
BUG: Fix linux audio and crypto
This commit is contained in:
Generated
+271
-18
@@ -73,6 +73,22 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotate-snippets"
|
||||||
|
version = "0.11.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.104"
|
version = "1.0.104"
|
||||||
@@ -230,7 +246,7 @@ dependencies = [
|
|||||||
"glib-sys",
|
"glib-sys",
|
||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -290,6 +306,25 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bindgen"
|
||||||
|
version = "0.72.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||||
|
dependencies = [
|
||||||
|
"annotate-snippets",
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
"cexpr",
|
||||||
|
"clang-sys",
|
||||||
|
"itertools",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"rustc-hash",
|
||||||
|
"shlex 1.3.0",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -439,7 +474,7 @@ checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"glib-sys",
|
"glib-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -491,7 +526,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
|
checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex 2.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -500,6 +535,15 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cexpr"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||||
|
dependencies = [
|
||||||
|
"nom 7.1.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfb"
|
name = "cfb"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@@ -518,7 +562,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
|
checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"target-lexicon",
|
"target-lexicon 0.12.16",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-expr"
|
||||||
|
version = "0.20.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe4ece8474b5f766c63426647e7b4b316b67431ade1036a8313cee24a03ae917"
|
||||||
|
dependencies = [
|
||||||
|
"smallvec",
|
||||||
|
"target-lexicon 0.13.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -573,6 +627,17 @@ dependencies = [
|
|||||||
"inout",
|
"inout",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clang-sys"
|
||||||
|
version = "1.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
|
||||||
|
dependencies = [
|
||||||
|
"glob",
|
||||||
|
"libc",
|
||||||
|
"libloading 0.8.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmov"
|
name = "cmov"
|
||||||
version = "0.5.4"
|
version = "0.5.4"
|
||||||
@@ -608,6 +673,12 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie-factory"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
@@ -672,6 +743,7 @@ dependencies = [
|
|||||||
"block2",
|
"block2",
|
||||||
"coreaudio-rs",
|
"coreaudio-rs",
|
||||||
"dasp_sample",
|
"dasp_sample",
|
||||||
|
"futures",
|
||||||
"jni 0.22.4",
|
"jni 0.22.4",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -687,6 +759,9 @@ dependencies = [
|
|||||||
"objc2-core-audio-types",
|
"objc2-core-audio-types",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
|
"pipewire",
|
||||||
|
"portable-atomic",
|
||||||
|
"pulseaudio",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"windows 0.62.2",
|
"windows 0.62.2",
|
||||||
"windows-core 0.62.2",
|
"windows-core 0.62.2",
|
||||||
@@ -1058,6 +1133,12 @@ version = "1.0.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embed-resource"
|
name = "embed-resource"
|
||||||
version = "3.0.11"
|
version = "3.0.11"
|
||||||
@@ -1099,6 +1180,17 @@ version = "1.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
|
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-primitive-derive"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba7795da175654fe16979af73f81f26a8ea27638d8d9823d317016888a63dc4c"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enumflags2"
|
name = "enumflags2"
|
||||||
version = "0.7.12"
|
version = "0.7.12"
|
||||||
@@ -1256,6 +1348,21 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.34"
|
version = "0.3.34"
|
||||||
@@ -1263,6 +1370,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
|
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1330,6 +1438,7 @@ version = "0.3.34"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
@@ -1378,7 +1487,7 @@ dependencies = [
|
|||||||
"glib-sys",
|
"glib-sys",
|
||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1395,7 +1504,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"pango-sys",
|
"pango-sys",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1409,7 +1518,7 @@ dependencies = [
|
|||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1435,7 +1544,7 @@ dependencies = [
|
|||||||
"gdk-sys",
|
"gdk-sys",
|
||||||
"glib-sys",
|
"glib-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
"x11",
|
"x11",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1512,7 +1621,7 @@ dependencies = [
|
|||||||
"glib-sys",
|
"glib-sys",
|
||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1560,7 +1669,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
|
checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1577,7 +1686,7 @@ checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"glib-sys",
|
"glib-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1616,7 +1725,7 @@ dependencies = [
|
|||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"pango-sys",
|
"pango-sys",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1979,6 +2088,15 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
@@ -2005,7 +2123,7 @@ dependencies = [
|
|||||||
"glib-sys",
|
"glib-sys",
|
||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2199,7 +2317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf"
|
checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk-sys",
|
"gtk-sys",
|
||||||
"libloading",
|
"libloading 0.7.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2228,6 +2346,16 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.20"
|
version = "0.1.20"
|
||||||
@@ -2237,6 +2365,33 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libspa"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "882f7427e7989dcc9d388b7f05c4630390a1d7696f9ffa469cd4a7a48f0b4c40"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
"cc",
|
||||||
|
"cookie-factory",
|
||||||
|
"libc",
|
||||||
|
"libspa-sys",
|
||||||
|
"nom 8.0.0",
|
||||||
|
"rustix",
|
||||||
|
"system-deps 7.0.8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libspa-sys"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b6e17bdaf63ed0d5e4144022624032b41fd9733112e8c74ac26fc9bf1291924"
|
||||||
|
dependencies = [
|
||||||
|
"bindgen",
|
||||||
|
"cc",
|
||||||
|
"system-deps 7.0.8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -2302,6 +2457,12 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -2319,6 +2480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
|
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
|
"log",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
@@ -2380,6 +2542,25 @@ version = "1.0.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "8.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-complex"
|
name = "num-complex"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@@ -2748,7 +2929,7 @@ dependencies = [
|
|||||||
"glib-sys",
|
"glib-sys",
|
||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2856,6 +3037,31 @@ dependencies = [
|
|||||||
"futures-io",
|
"futures-io",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pipewire"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bde71084c4e25959d68f1ea54daa75e5ecdb338e5caf0b5510143b79baa32d5c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
"libc",
|
||||||
|
"libspa",
|
||||||
|
"libspa-sys",
|
||||||
|
"pipewire-sys",
|
||||||
|
"rustix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pipewire-sys"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ce653f53e63e5b93853218092ee9a8906a5d082c92f3f1db26316955dd63ce0"
|
||||||
|
dependencies = [
|
||||||
|
"bindgen",
|
||||||
|
"libspa-sys",
|
||||||
|
"system-deps 7.0.8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.34"
|
version = "0.3.34"
|
||||||
@@ -3032,6 +3238,22 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pulseaudio"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d70623bd7967a9ca4c2ae0e807fc380b291f98480fc037042305ec643a4d3373"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
"byteorder",
|
||||||
|
"enum-primitive-derive",
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"mio",
|
||||||
|
"num-traits",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.41.0"
|
version = "0.41.0"
|
||||||
@@ -3526,6 +3748,12 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@@ -3637,7 +3865,7 @@ dependencies = [
|
|||||||
"glib-sys",
|
"glib-sys",
|
||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3751,13 +3979,26 @@ version = "6.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
|
checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-expr",
|
"cfg-expr 0.15.8",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"toml 0.8.2",
|
"toml 0.8.2",
|
||||||
"version-compare",
|
"version-compare",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-deps"
|
||||||
|
version = "7.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-expr 0.20.9",
|
||||||
|
"heck 0.5.0",
|
||||||
|
"pkg-config",
|
||||||
|
"toml 1.1.4+spec-1.1.0",
|
||||||
|
"version-compare",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tao"
|
name = "tao"
|
||||||
version = "0.35.3"
|
version = "0.35.3"
|
||||||
@@ -3815,6 +4056,12 @@ version = "0.12.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "target-lexicon"
|
||||||
|
version = "0.13.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "2.11.5"
|
version = "2.11.5"
|
||||||
@@ -4520,6 +4767,12 @@ version = "1.13.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "universal-hash"
|
name = "universal-hash"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@@ -4781,7 +5034,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"soup3-sys",
|
"soup3-sys",
|
||||||
"system-deps",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ tauri = { version = "2", features = [] }
|
|||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
cpal = "0.18.2"
|
cpal = { version = "0.18.2", features = ["pipewire", "pulseaudio"] }
|
||||||
ringbuf = "0.5.1"
|
ringbuf = "0.5.1"
|
||||||
rubato = "5.0.0"
|
rubato = "5.0.0"
|
||||||
chacha20poly1305 = "0.11.0"
|
chacha20poly1305 = "0.11.0"
|
||||||
|
|||||||
+254
-46
@@ -10,7 +10,10 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use chacha20poly1305::{aead::KeyInit, ChaCha20Poly1305, Key};
|
use chacha20poly1305::{aead::KeyInit, ChaCha20Poly1305, Key};
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::{
|
||||||
|
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||||
|
FromSample, SampleFormat, SizedSample,
|
||||||
|
};
|
||||||
use ringbuf::{
|
use ringbuf::{
|
||||||
storage::Heap,
|
storage::Heap,
|
||||||
traits::{Consumer, Observer, Producer, Split},
|
traits::{Consumer, Observer, Producer, Split},
|
||||||
@@ -178,24 +181,67 @@ fn build_input_stream(
|
|||||||
producer: AudioProducer,
|
producer: AudioProducer,
|
||||||
state_inner: Arc<VoiceStateInner>,
|
state_inner: Arc<VoiceStateInner>,
|
||||||
) -> Result<cpal::Stream, String> {
|
) -> Result<cpal::Stream, String> {
|
||||||
|
let sample_format = device
|
||||||
|
.default_input_config()
|
||||||
|
.map_err(|e| format!("Failed to get input config: {e}"))?
|
||||||
|
.sample_format();
|
||||||
|
|
||||||
|
match sample_format {
|
||||||
|
SampleFormat::F32 => build_input_stream_with::<f32>(device, config, producer, state_inner),
|
||||||
|
SampleFormat::I16 => build_input_stream_with::<i16>(device, config, producer, state_inner),
|
||||||
|
SampleFormat::I32 => build_input_stream_with::<i32>(device, config, producer, state_inner),
|
||||||
|
SampleFormat::I64 => build_input_stream_with::<i64>(device, config, producer, state_inner),
|
||||||
|
SampleFormat::U8 => build_input_stream_with::<u8>(device, config, producer, state_inner),
|
||||||
|
SampleFormat::U16 => build_input_stream_with::<u16>(device, config, producer, state_inner),
|
||||||
|
SampleFormat::U32 => build_input_stream_with::<u32>(device, config, producer, state_inner),
|
||||||
|
SampleFormat::U64 => build_input_stream_with::<u64>(device, config, producer, state_inner),
|
||||||
|
format => Err(format!("Unsupported input sample format: {format}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_input_stream_with<T>(
|
||||||
|
device: &cpal::Device,
|
||||||
|
config: cpal::StreamConfig,
|
||||||
|
producer: AudioProducer,
|
||||||
|
state_inner: Arc<VoiceStateInner>,
|
||||||
|
) -> Result<cpal::Stream, String>
|
||||||
|
where
|
||||||
|
T: SizedSample + Send + 'static,
|
||||||
|
f32: FromSample<T>,
|
||||||
|
{
|
||||||
let native_sample_rate = config.sample_rate;
|
let native_sample_rate = config.sample_rate;
|
||||||
let channels = config.channels as usize;
|
let channels = config.channels as usize;
|
||||||
let mut resampler = LinearResampler::new();
|
let mut resampler = LinearResampler::new();
|
||||||
let mut mono_buffer = Vec::with_capacity(2048);
|
let mut mono_buffer = Vec::with_capacity(2048);
|
||||||
let mut resampled_buffer = Vec::with_capacity(2048);
|
let mut resampled_buffer = Vec::with_capacity(2048);
|
||||||
|
let mut callback_count: u64 = 0;
|
||||||
|
|
||||||
let inner_input_err = Arc::clone(&state_inner);
|
let inner_input_err = Arc::clone(&state_inner);
|
||||||
|
|
||||||
device
|
device
|
||||||
.build_input_stream(
|
.build_input_stream(
|
||||||
config,
|
config,
|
||||||
move |data: &[f32], _| {
|
move |data: &[T], _| {
|
||||||
|
callback_count += 1;
|
||||||
|
if callback_count % 200 == 0 {
|
||||||
|
let peak: f32 = data
|
||||||
|
.iter()
|
||||||
|
.map(|&s| s.to_sample::<f32>().abs())
|
||||||
|
.fold(0.0f32, f32::max);
|
||||||
|
eprintln!(
|
||||||
|
"[vc] input callback #{callback_count}: {} frames, {} ch, peak {:.4}",
|
||||||
|
data.len() / channels,
|
||||||
|
channels,
|
||||||
|
peak
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
mono_buffer.clear();
|
mono_buffer.clear();
|
||||||
resampled_buffer.clear();
|
resampled_buffer.clear();
|
||||||
|
|
||||||
// Downmix channels to mono
|
// Downmix channels to mono, converting to f32
|
||||||
for chunk in data.chunks_exact(channels) {
|
for chunk in data.chunks_exact(channels) {
|
||||||
let sum: f32 = chunk.iter().sum();
|
let sum: f32 = chunk.iter().map(|&s| s.to_sample::<f32>()).sum();
|
||||||
mono_buffer.push(sum / channels as f32);
|
mono_buffer.push(sum / channels as f32);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,13 +259,21 @@ fn build_input_stream(
|
|||||||
},
|
},
|
||||||
move |err| {
|
move |err| {
|
||||||
eprintln!("[vc] Input error: {err}. Attempting recovery...");
|
eprintln!("[vc] Input error: {err}. Attempting recovery...");
|
||||||
if let Ok(mut lock) = inner_input_err.session.lock() {
|
let rec = Arc::clone(&inner_input_err);
|
||||||
|
thread::spawn(move || {
|
||||||
|
// Give the errored stream a moment to unwind before rebuilding.
|
||||||
|
thread::sleep(Duration::from_millis(10));
|
||||||
|
let Ok(mut lock) = rec.session.lock() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
if let Some(session) = lock.as_mut() {
|
if let Some(session) = lock.as_mut() {
|
||||||
|
if session.shutdown.load(Ordering::SeqCst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let target_device = session.current_input_device.clone();
|
let target_device = session.current_input_device.clone();
|
||||||
let _ = session
|
let _ = session.update_input_device(target_device, Arc::clone(&rec));
|
||||||
.update_input_device(target_device, Arc::clone(&inner_input_err));
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@@ -233,25 +287,64 @@ fn build_output_stream(
|
|||||||
consumer: AudioConsumer,
|
consumer: AudioConsumer,
|
||||||
state_inner: Arc<VoiceStateInner>,
|
state_inner: Arc<VoiceStateInner>,
|
||||||
) -> Result<cpal::Stream, String> {
|
) -> Result<cpal::Stream, String> {
|
||||||
|
let sample_format = device
|
||||||
|
.default_output_config()
|
||||||
|
.map_err(|e| format!("Failed to get output config: {e}"))?
|
||||||
|
.sample_format();
|
||||||
|
|
||||||
|
match sample_format {
|
||||||
|
SampleFormat::F32 => build_output_stream_with::<f32>(device, config, consumer, state_inner),
|
||||||
|
SampleFormat::I16 => build_output_stream_with::<i16>(device, config, consumer, state_inner),
|
||||||
|
SampleFormat::I32 => build_output_stream_with::<i32>(device, config, consumer, state_inner),
|
||||||
|
SampleFormat::I64 => build_output_stream_with::<i64>(device, config, consumer, state_inner),
|
||||||
|
SampleFormat::U8 => build_output_stream_with::<u8>(device, config, consumer, state_inner),
|
||||||
|
SampleFormat::U16 => build_output_stream_with::<u16>(device, config, consumer, state_inner),
|
||||||
|
SampleFormat::U32 => build_output_stream_with::<u32>(device, config, consumer, state_inner),
|
||||||
|
SampleFormat::U64 => build_output_stream_with::<u64>(device, config, consumer, state_inner),
|
||||||
|
format => Err(format!("Unsupported output sample format: {format}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_output_stream_with<T>(
|
||||||
|
device: &cpal::Device,
|
||||||
|
config: cpal::StreamConfig,
|
||||||
|
consumer: AudioConsumer,
|
||||||
|
state_inner: Arc<VoiceStateInner>,
|
||||||
|
) -> Result<cpal::Stream, String>
|
||||||
|
where
|
||||||
|
T: SizedSample + Send + 'static,
|
||||||
|
T: FromSample<f32>,
|
||||||
|
{
|
||||||
let native_sample_rate = config.sample_rate;
|
let native_sample_rate = config.sample_rate;
|
||||||
let channels = config.channels as usize;
|
let channels = config.channels as usize;
|
||||||
let mut resampler = LinearResampler::new();
|
let mut resampler = LinearResampler::new();
|
||||||
let mut raw_mono_samples = Vec::with_capacity(2048);
|
let mut raw_mono_samples = Vec::with_capacity(2048);
|
||||||
let mut resampled_mono = Vec::with_capacity(2048);
|
let mut resampled_mono = Vec::with_capacity(2048);
|
||||||
let mut last_sample = 0.0f32;
|
let mut last_sample = 0.0f32;
|
||||||
|
let mut callback_count: u64 = 0;
|
||||||
|
|
||||||
let inner_output_err = Arc::clone(&state_inner);
|
let inner_output_err = Arc::clone(&state_inner);
|
||||||
|
|
||||||
device
|
device
|
||||||
.build_output_stream(
|
.build_output_stream(
|
||||||
config,
|
config,
|
||||||
move |data: &mut [f32], _| {
|
move |data: &mut [T], _| {
|
||||||
|
callback_count += 1;
|
||||||
|
if callback_count % 200 == 0 {
|
||||||
|
eprintln!(
|
||||||
|
"[vc] output callback #{callback_count}: {} frames, {} ch",
|
||||||
|
data.len() / channels,
|
||||||
|
channels
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let required_mono_samples = (data.len() / channels) * TARGET_SAMPLE_RATE as usize
|
let required_mono_samples = (data.len() / channels) * TARGET_SAMPLE_RATE as usize
|
||||||
/ native_sample_rate as usize;
|
/ native_sample_rate as usize;
|
||||||
|
|
||||||
raw_mono_samples.clear();
|
raw_mono_samples.clear();
|
||||||
resampled_mono.clear();
|
resampled_mono.clear();
|
||||||
|
|
||||||
|
let mut underflow_count = 0usize;
|
||||||
if let Ok(mut cons) = consumer.lock() {
|
if let Ok(mut cons) = consumer.lock() {
|
||||||
for _ in 0..required_mono_samples {
|
for _ in 0..required_mono_samples {
|
||||||
if let Some(s) = cons.try_pop() {
|
if let Some(s) = cons.try_pop() {
|
||||||
@@ -261,9 +354,18 @@ fn build_output_stream(
|
|||||||
// Exponential decay to prevent clicking when underflowing
|
// Exponential decay to prevent clicking when underflowing
|
||||||
last_sample *= 0.92;
|
last_sample *= 0.92;
|
||||||
raw_mono_samples.push(last_sample);
|
raw_mono_samples.push(last_sample);
|
||||||
|
underflow_count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if callback_count % 200 == 0 {
|
||||||
|
eprintln!(
|
||||||
|
"[vc] output: got {}/{} mono samples ({} underflow)",
|
||||||
|
required_mono_samples - underflow_count,
|
||||||
|
required_mono_samples,
|
||||||
|
underflow_count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Resample from 48kHz mono to target native output rate
|
// Resample from 48kHz mono to target native output rate
|
||||||
resampler.process(
|
resampler.process(
|
||||||
@@ -280,7 +382,7 @@ fn build_output_stream(
|
|||||||
let mono_val = resampled_mono[res_idx];
|
let mono_val = resampled_mono[res_idx];
|
||||||
for ch in 0..channels {
|
for ch in 0..channels {
|
||||||
if out_idx + ch < data.len() {
|
if out_idx + ch < data.len() {
|
||||||
data[out_idx + ch] = mono_val;
|
data[out_idx + ch] = T::from_sample(mono_val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out_idx += channels;
|
out_idx += channels;
|
||||||
@@ -289,13 +391,20 @@ fn build_output_stream(
|
|||||||
},
|
},
|
||||||
move |err| {
|
move |err| {
|
||||||
eprintln!("[vc] Output error: {err}. Attempting recovery...");
|
eprintln!("[vc] Output error: {err}. Attempting recovery...");
|
||||||
if let Ok(mut lock) = inner_output_err.session.lock() {
|
let rec = Arc::clone(&inner_output_err);
|
||||||
|
thread::spawn(move || {
|
||||||
|
thread::sleep(Duration::from_millis(10));
|
||||||
|
let Ok(mut lock) = rec.session.lock() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
if let Some(session) = lock.as_mut() {
|
if let Some(session) = lock.as_mut() {
|
||||||
|
if session.shutdown.load(Ordering::SeqCst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let target_device = session.current_output_device.clone();
|
let target_device = session.current_output_device.clone();
|
||||||
let _ = session
|
let _ = session.update_output_device(target_device, Arc::clone(&rec));
|
||||||
.update_output_device(target_device, Arc::clone(&inner_output_err));
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@@ -324,18 +433,22 @@ pub fn list_output_devices() -> Result<Vec<String>, String> {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn disconnect_from_vc(voice_state: State<'_, VoiceState>) -> Result<(), String> {
|
pub fn disconnect_from_vc(voice_state: State<'_, VoiceState>) -> Result<(), String> {
|
||||||
let mut lock = voice_state
|
let session = {
|
||||||
.inner
|
let mut lock = voice_state
|
||||||
.session
|
.inner
|
||||||
.lock()
|
.session
|
||||||
.map_err(|e| e.to_string())?;
|
.lock()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
lock.take()
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(session) = lock.take() {
|
if let Some(session) = session {
|
||||||
session.shutdown.store(true, Ordering::SeqCst);
|
session.shutdown.store(true, Ordering::SeqCst);
|
||||||
let _ = session.input_stream.pause();
|
let _ = session.input_stream.pause();
|
||||||
if let Ok(output) = session.output_stream.lock() {
|
if let Ok(output) = session.output_stream.lock() {
|
||||||
let _ = output.pause();
|
let _ = output.pause();
|
||||||
}
|
}
|
||||||
|
eprintln!("[vc] disconnected and paused streams");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -349,59 +462,102 @@ pub fn connect_to_vc(
|
|||||||
config_state: State<'_, ConfigState>,
|
config_state: State<'_, ConfigState>,
|
||||||
voice_state: State<'_, VoiceState>,
|
voice_state: State<'_, VoiceState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
disconnect_from_vc(voice_state.clone())?;
|
eprintln!("[vc] connect_to_vc called hostname={hostname} pin={pin}");
|
||||||
|
|
||||||
let key = Key::try_from(shared_secret.as_slice()).map_err(|v| v.to_string())?;
|
if let Err(e) = disconnect_from_vc(voice_state.clone()) {
|
||||||
|
eprintln!("[vc] disconnect_from_vc failed: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = match Key::try_from(shared_secret.as_slice()) {
|
||||||
|
Ok(k) => k,
|
||||||
|
Err(v) => {
|
||||||
|
let msg = v.to_string();
|
||||||
|
eprintln!("[vc] invalid key length: {msg}");
|
||||||
|
return Err(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
let cipher = Arc::new(Mutex::new(SessionCipher::new(ChaCha20Poly1305::new(&key))));
|
let cipher = Arc::new(Mutex::new(SessionCipher::new(ChaCha20Poly1305::new(&key))));
|
||||||
|
|
||||||
let config = config_state.0.lock().unwrap().clone();
|
let config = config_state.0.lock().unwrap().clone();
|
||||||
let state_inner = Arc::clone(&voice_state.inner);
|
let state_inner = Arc::clone(&voice_state.inner);
|
||||||
|
|
||||||
let socket = Arc::new(
|
let socket = match UdpSocket::bind("0.0.0.0:0") {
|
||||||
UdpSocket::bind("0.0.0.0:0").map_err(|e| format!("Failed to bind UDP socket: {e}"))?,
|
Ok(s) => s,
|
||||||
);
|
Err(e) => {
|
||||||
socket
|
let msg = format!("Failed to bind UDP socket: {e}");
|
||||||
.connect(&hostname)
|
eprintln!("[vc] {msg}");
|
||||||
.map_err(|e| format!("Failed to connect UDP socket: {e}"))?;
|
return Err(msg);
|
||||||
socket
|
}
|
||||||
.set_read_timeout(Some(Duration::from_millis(5)))
|
};
|
||||||
.map_err(|e| e.to_string())?;
|
let socket = Arc::new(socket);
|
||||||
socket
|
if let Err(e) = socket.connect(&hostname) {
|
||||||
.send(&pin.to_be_bytes())
|
let msg = format!("Failed to connect UDP socket: {e}");
|
||||||
.map_err(|e| format!("Failed to send pin: {e}"))?;
|
eprintln!("[vc] {msg}");
|
||||||
|
return Err(msg);
|
||||||
|
}
|
||||||
|
if let Err(e) = socket.set_read_timeout(Some(Duration::from_millis(5))) {
|
||||||
|
let msg = e.to_string();
|
||||||
|
eprintln!("[vc] set_read_timeout failed: {msg}");
|
||||||
|
return Err(msg);
|
||||||
|
}
|
||||||
|
if let Err(e) = socket.send(&pin.to_be_bytes()) {
|
||||||
|
let msg = format!("Failed to send pin: {e}");
|
||||||
|
eprintln!("[vc] {msg}");
|
||||||
|
return Err(msg);
|
||||||
|
}
|
||||||
|
|
||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
|
|
||||||
let input_device = match &config.input_device_name {
|
let input_device = match &config.input_device_name {
|
||||||
Some(name) => host
|
Some(name) => host
|
||||||
.input_devices()
|
.input_devices()
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| {
|
||||||
|
let msg = e.to_string();
|
||||||
|
eprintln!("[vc] failed to enumerate input devices: {msg}");
|
||||||
|
msg
|
||||||
|
})?
|
||||||
.find(|d| {
|
.find(|d| {
|
||||||
d.description()
|
d.description()
|
||||||
.ok()
|
.ok()
|
||||||
.map(|x| x.name() == *name)
|
.map(|x| x.name() == *name)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
})
|
})
|
||||||
.ok_or("Input device not found")?,
|
.ok_or_else(|| {
|
||||||
None => host
|
let msg = format!("Input device not found: {name}");
|
||||||
.default_input_device()
|
eprintln!("[vc] {msg}");
|
||||||
.ok_or("No default input device")?,
|
msg
|
||||||
|
})?,
|
||||||
|
None => host.default_input_device().ok_or_else(|| {
|
||||||
|
let msg = "No default input device".to_string();
|
||||||
|
eprintln!("[vc] {msg}");
|
||||||
|
msg
|
||||||
|
})?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let output_device = match &config.output_device_name {
|
let output_device = match &config.output_device_name {
|
||||||
Some(name) => host
|
Some(name) => host
|
||||||
.output_devices()
|
.output_devices()
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| {
|
||||||
|
let msg = e.to_string();
|
||||||
|
eprintln!("[vc] failed to enumerate output devices: {msg}");
|
||||||
|
msg
|
||||||
|
})?
|
||||||
.find(|d| {
|
.find(|d| {
|
||||||
d.description()
|
d.description()
|
||||||
.ok()
|
.ok()
|
||||||
.map(|x| x.name() == *name)
|
.map(|x| x.name() == *name)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
})
|
})
|
||||||
.ok_or("Output device not found")?,
|
.ok_or_else(|| {
|
||||||
None => host
|
let msg = format!("Output device not found: {name}");
|
||||||
.default_output_device()
|
eprintln!("[vc] {msg}");
|
||||||
.ok_or("No default output device")?,
|
msg
|
||||||
|
})?,
|
||||||
|
None => host.default_output_device().ok_or_else(|| {
|
||||||
|
let msg = "No default output device".to_string();
|
||||||
|
eprintln!("[vc] {msg}");
|
||||||
|
msg
|
||||||
|
})?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let shutdown = Arc::new(AtomicBool::new(false));
|
let shutdown = Arc::new(AtomicBool::new(false));
|
||||||
@@ -417,6 +573,12 @@ pub fn connect_to_vc(
|
|||||||
.map_err(|e| format!("Failed to get default output config: {e}"))?
|
.map_err(|e| format!("Failed to get default output config: {e}"))?
|
||||||
.config();
|
.config();
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"[vc] Output device {:?} config: {} Hz, {} ch",
|
||||||
|
output_device.description().map(|d| d.name().to_string()),
|
||||||
|
output_config.sample_rate,
|
||||||
|
output_config.channels
|
||||||
|
);
|
||||||
let output_stream = build_output_stream(
|
let output_stream = build_output_stream(
|
||||||
&output_device,
|
&output_device,
|
||||||
output_config,
|
output_config,
|
||||||
@@ -437,6 +599,12 @@ pub fn connect_to_vc(
|
|||||||
.map_err(|e| format!("Failed to get default input config: {e}"))?
|
.map_err(|e| format!("Failed to get default input config: {e}"))?
|
||||||
.config();
|
.config();
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"[vc] Input device {:?} config: {} Hz, {} ch",
|
||||||
|
input_device.description().map(|d| d.name().to_string()),
|
||||||
|
input_config.sample_rate,
|
||||||
|
input_config.channels
|
||||||
|
);
|
||||||
let input_stream = build_input_stream(
|
let input_stream = build_input_stream(
|
||||||
&input_device,
|
&input_device,
|
||||||
input_config,
|
input_config,
|
||||||
@@ -454,6 +622,9 @@ pub fn connect_to_vc(
|
|||||||
let mut sequence = 0u32;
|
let mut sequence = 0u32;
|
||||||
let mut frame_buf = vec![0.0f32; PACKET_SAMPLES];
|
let mut frame_buf = vec![0.0f32; PACKET_SAMPLES];
|
||||||
let mut hangover_counter = 0;
|
let mut hangover_counter = 0;
|
||||||
|
let mut loop_count: u64 = 0;
|
||||||
|
// Adaptive VAD: track a slow-moving noise floor so quiet mics still trigger.
|
||||||
|
let mut noise_floor: f32 = 0.0;
|
||||||
|
|
||||||
while !shutdown.load(Ordering::Relaxed) {
|
while !shutdown.load(Ordering::Relaxed) {
|
||||||
if consumer_in.occupied_len() >= PACKET_SAMPLES {
|
if consumer_in.occupied_len() >= PACKET_SAMPLES {
|
||||||
@@ -462,7 +633,17 @@ pub fn connect_to_vc(
|
|||||||
let sum_squares: f32 = frame_buf.iter().map(|&s| s * s).sum();
|
let sum_squares: f32 = frame_buf.iter().map(|&s| s * s).sum();
|
||||||
let rms = (sum_squares / PACKET_SAMPLES as f32).sqrt();
|
let rms = (sum_squares / PACKET_SAMPLES as f32).sqrt();
|
||||||
|
|
||||||
let is_speaking = if rms >= VAD_THRESHOLD {
|
// Update noise floor (attack fast, release slow).
|
||||||
|
if noise_floor == 0.0 {
|
||||||
|
noise_floor = rms;
|
||||||
|
} else if rms < noise_floor {
|
||||||
|
noise_floor = noise_floor * 0.9 + rms * 0.1;
|
||||||
|
} else {
|
||||||
|
noise_floor = noise_floor * 0.999;
|
||||||
|
}
|
||||||
|
let threshold = (noise_floor * 4.0).max(VAD_THRESHOLD);
|
||||||
|
|
||||||
|
let is_speaking = if rms >= threshold {
|
||||||
hangover_counter = VAD_HANGOVER_FRAMES;
|
hangover_counter = VAD_HANGOVER_FRAMES;
|
||||||
true
|
true
|
||||||
} else if hangover_counter > 0 {
|
} else if hangover_counter > 0 {
|
||||||
@@ -472,6 +653,14 @@ pub fn connect_to_vc(
|
|||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
loop_count += 1;
|
||||||
|
if loop_count % 200 == 0 {
|
||||||
|
eprintln!(
|
||||||
|
"[vc] sender: buffered {} samples, frame rms {rms:.4}, floor {noise_floor:.4}, thr {threshold:.4}, speaking {is_speaking}, seq {sequence}",
|
||||||
|
consumer_in.occupied_len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if is_speaking {
|
if is_speaking {
|
||||||
// sequence goes INSIDE the plaintext now, prefixed before the PCM
|
// sequence goes INSIDE the plaintext now, prefixed before the PCM
|
||||||
let mut plaintext = Vec::with_capacity(4 + PACKET_SAMPLES * 2);
|
let mut plaintext = Vec::with_capacity(4 + PACKET_SAMPLES * 2);
|
||||||
@@ -488,7 +677,15 @@ pub fn connect_to_vc(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = input_socket.send(&net_packet);
|
let sent = input_socket.send(&net_packet);
|
||||||
|
if let Err(e) = sent {
|
||||||
|
eprintln!("[vc] sender: send failed: {e}");
|
||||||
|
} else if loop_count % 200 == 0 {
|
||||||
|
eprintln!(
|
||||||
|
"[vc] sender: sent packet seq {sequence} ({} bytes)",
|
||||||
|
net_packet.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
sequence = sequence.wrapping_add(1);
|
sequence = sequence.wrapping_add(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -511,9 +708,13 @@ pub fn connect_to_vc(
|
|||||||
let mut last_good_frame = vec![0.0f32; PACKET_SAMPLES];
|
let mut last_good_frame = vec![0.0f32; PACKET_SAMPLES];
|
||||||
let mut udp_buffer = [0u8; MAX_PACKET_SIZE];
|
let mut udp_buffer = [0u8; MAX_PACKET_SIZE];
|
||||||
let mut next_frame_time = Instant::now();
|
let mut next_frame_time = Instant::now();
|
||||||
|
let mut recv_count: u64 = 0;
|
||||||
|
let mut loop_count: u64 = 0;
|
||||||
|
|
||||||
while !shutdown.load(Ordering::Relaxed) {
|
while !shutdown.load(Ordering::Relaxed) {
|
||||||
|
loop_count += 1;
|
||||||
if let Ok(len) = socket.recv(&mut udp_buffer) {
|
if let Ok(len) = socket.recv(&mut udp_buffer) {
|
||||||
|
recv_count += 1;
|
||||||
match cipher.lock().unwrap().decrypt(&udp_buffer[..len]) {
|
match cipher.lock().unwrap().decrypt(&udp_buffer[..len]) {
|
||||||
Ok(plaintext) => {
|
Ok(plaintext) => {
|
||||||
if plaintext.len() < 4 {
|
if plaintext.len() < 4 {
|
||||||
@@ -536,6 +737,13 @@ pub fn connect_to_vc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if loop_count % 2000 == 0 {
|
||||||
|
eprintln!(
|
||||||
|
"[vc] receiver: received {recv_count} packets total, {} buffered, prebuffering {is_prebuffering}, expected {expected:?}",
|
||||||
|
packets.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if is_prebuffering {
|
if is_prebuffering {
|
||||||
if packets.len() >= INITIAL_PACKET_CUSHION {
|
if packets.len() >= INITIAL_PACKET_CUSHION {
|
||||||
expected = packets.keys().next().copied();
|
expected = packets.keys().next().copied();
|
||||||
|
|||||||
+6
-11
@@ -3,7 +3,6 @@ use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce};
|
|||||||
pub struct SessionCipher {
|
pub struct SessionCipher {
|
||||||
cipher: ChaCha20Poly1305,
|
cipher: ChaCha20Poly1305,
|
||||||
send_counter: u64,
|
send_counter: u64,
|
||||||
recv_counter: u64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionCipher {
|
impl SessionCipher {
|
||||||
@@ -11,11 +10,10 @@ impl SessionCipher {
|
|||||||
Self {
|
Self {
|
||||||
cipher,
|
cipher,
|
||||||
send_counter: 0,
|
send_counter: 0,
|
||||||
recv_counter: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_send_nonce(&mut self) -> [u8; 12] {
|
fn next_send_nonce(&mut self) -> [u8; 12] {
|
||||||
let mut nonce = [0u8; 12];
|
let mut nonce = [0u8; 12];
|
||||||
nonce[..8].copy_from_slice(&self.send_counter.to_be_bytes());
|
nonce[..8].copy_from_slice(&self.send_counter.to_be_bytes());
|
||||||
// top bit distinguishes "send" direction from "recv" direction,
|
// top bit distinguishes "send" direction from "recv" direction,
|
||||||
@@ -26,13 +24,6 @@ impl SessionCipher {
|
|||||||
nonce
|
nonce
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_recv_nonce(&mut self) -> [u8; 12] {
|
|
||||||
let mut nonce = [0u8; 12];
|
|
||||||
nonce[..8].copy_from_slice(&self.recv_counter.to_be_bytes());
|
|
||||||
self.recv_counter += 1;
|
|
||||||
nonce
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, String> {
|
pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, String> {
|
||||||
let nonce_bytes = self.next_send_nonce();
|
let nonce_bytes = self.next_send_nonce();
|
||||||
let nonce = Nonce::try_from(nonce_bytes).map_err(|v| v.to_string())?;
|
let nonce = Nonce::try_from(nonce_bytes).map_err(|v| v.to_string())?;
|
||||||
@@ -48,11 +39,15 @@ impl SessionCipher {
|
|||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrypt(&mut self, data: &[u8]) -> Result<Vec<u8>, String> {
|
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
|
||||||
if data.len() < 12 {
|
if data.len() < 12 {
|
||||||
return Err("message too short to contain a nonce".to_string());
|
return Err("message too short to contain a nonce".to_string());
|
||||||
}
|
}
|
||||||
let (nonce_bytes, ciphertext) = data.split_at(12);
|
let (nonce_bytes, ciphertext) = data.split_at(12);
|
||||||
|
|
||||||
|
// Use the nonce that travelled with this packet rather than a locally
|
||||||
|
// tracked counter. This makes decryption immune to UDP packet loss or
|
||||||
|
// reordering, because we never desync from the sender's counter.
|
||||||
let nonce = Nonce::try_from(nonce_bytes).map_err(|v| v.to_string())?;
|
let nonce = Nonce::try_from(nonce_bytes).map_err(|v| v.to_string())?;
|
||||||
|
|
||||||
self.cipher
|
self.cipher
|
||||||
|
|||||||
Reference in New Issue
Block a user