refactored tap core into its own crate to eventually support a gui

This commit is contained in:
joeyahines committed 2026-10-04 17:55:51 -06:00
1 parent a9f2951bf0
commit 43b1aaa9f7
11 files changed
+327 -351

No files matched your search

Generated
+1
View File
@@ -3,6 +3,7 @@
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tap_core/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
Generated
+103 -111
View File
@@ -33,12 +33,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -161,16 +155,15 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "chrono"
version = "0.4.41"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link 0.1.3",
"windows-link 0.2.1",
]
[[package]]
@@ -210,7 +203,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -327,7 +320,7 @@ checksum = "ba7795da175654fe16979af73f81f26a8ea27638d8d9823d317016888a63dc4c"
dependencies = [
"num-traits",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -361,9 +354,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.60.2",
@@ -371,9 +364,9 @@ dependencies = [
[[package]]
name = "fastrand"
version = "2.3.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
[[package]]
name = "find-msvc-tools"
@@ -388,10 +381,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "futures"
version = "0.3.33"
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "futures"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
dependencies = [
"futures-channel",
"futures-core",
@@ -404,9 +403,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
dependencies = [
"futures-core",
"futures-sink",
@@ -414,15 +413,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-executor"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458"
checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
dependencies = [
"futures-core",
"futures-task",
@@ -431,38 +430,38 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
[[package]]
name = "futures-macro"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 3.0.6",
]
[[package]]
name = "futures-sink"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
[[package]]
name = "futures-task"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-channel",
"futures-core",
@@ -475,23 +474,14 @@ dependencies = [
"slab",
]
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi 0.14.3+wasi-0.2.4",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "heck"
@@ -575,7 +565,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -590,7 +580,7 @@ dependencies = [
"jni-sys 0.4.1",
"log",
"simd_cesu8",
"thiserror 2.0.12",
"thiserror 2.0.21",
"walkdir",
"windows-link 0.2.1",
]
@@ -605,7 +595,7 @@ dependencies = [
"quote",
"rustc_version",
"simd_cesu8",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -630,7 +620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
dependencies = [
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -651,9 +641,9 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "log"
@@ -675,13 +665,13 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "mio"
version = "1.2.2"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
dependencies = [
"libc",
"log",
"wasi 0.11.1+wasi-snapshot-preview1",
"wasi",
"windows-sys 0.61.2",
]
@@ -728,7 +718,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -759,7 +749,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -884,11 +874,12 @@ dependencies = [
[[package]]
name = "petgraph"
version = "0.7.1"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
dependencies = [
"fixedbitset",
"hashbrown",
"indexmap",
]
@@ -926,7 +917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2"
dependencies = [
"proc-macro2",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -959,21 +950,20 @@ dependencies = [
[[package]]
name = "prost-build"
version = "0.14.1"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1"
checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042"
dependencies = [
"heck",
"itertools",
"log",
"multimap",
"once_cell",
"petgraph",
"prettyplease",
"prost",
"prost-types",
"regex",
"syn",
"syn 2.0.104",
"tempfile",
]
@@ -987,14 +977,14 @@ dependencies = [
"itertools",
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
name = "prost-types"
version = "0.14.1"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72"
checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a"
dependencies = [
"prost",
]
@@ -1024,12 +1014,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "regex"
version = "1.11.1"
@@ -1070,9 +1054,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.0.8"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
dependencies = [
"bitflags",
"errno",
@@ -1119,7 +1103,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -1167,6 +1151,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tap"
version = "0.1.0"
@@ -1179,11 +1174,9 @@ dependencies = [
"env_logger",
"hound",
"log",
"opus",
"opusic-sys",
"prost",
"tap-interface",
"thiserror 2.0.12",
"tap_core",
"thiserror 2.0.21",
]
[[package]]
@@ -1197,14 +1190,28 @@ dependencies = [
"prost-types",
]
[[package]]
name = "tap_core"
version = "0.1.0"
dependencies = [
"chrono",
"circular-buffer",
"cpal",
"log",
"opus",
"opusic-sys",
"prost",
"tap-interface",
"thiserror 2.0.21",
]
[[package]]
name = "tempfile"
version = "3.21.0"
version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys 0.60.2",
@@ -1221,11 +1228,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.12"
version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
dependencies = [
"thiserror-impl 2.0.12",
"thiserror-impl 2.0.21",
]
[[package]]
@@ -1236,18 +1243,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 3.0.6",
]
[[package]]
@@ -1295,15 +1302,6 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
version = "0.14.3+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
@@ -1326,7 +1324,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
"wasm-bindgen-shared",
]
@@ -1348,7 +1346,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -1447,7 +1445,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -1458,7 +1456,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -1692,9 +1690,3 @@ checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"
+5 -4
View File
@@ -4,19 +4,20 @@ version = "0.1.0"
edition = "2024"
[dependencies]
cpal = { version = "0.18.2", features = ["pulseaudio"] }
clap = { version = "4.5.43", features = ["derive"] }
anyhow = "1.0.98"
log = "0.4.27"
env_logger = "0.11.8"
opus= { version="0.4.0" }
thiserror = "2.0.12"
circular-buffer = "2.0.1"
chrono = "0.4.41"
hound = "3.5.1"
tap-interface = {version = "0.1.0", registry = "ahines"}
prost = "0.14.1"
opusic-sys = { version = "0.7.5", features = ["bundled", "no-stack-protector", "no-hardening", "no-fortify-source"] }
tap_core = {path = "tap_core"}
cpal = "0.18.2"
[workspace]
members = ["tap_core"]
[workspace.metadata.cross.target.x86_64-pc-windows-gnu]
pre-build = [
-82
View File
@@ -1,82 +0,0 @@
use crate::udp_connection::OpusUdpConnection;
use crate::util::{SAMPLE_RATE, SAMPLE_RATE_RAW, STEREO_FRAME_SIZE, TAP_PACKET_MAX_DATA_LEN};
use anyhow::Error;
use chrono::Utc;
use log::{error, warn};
use opus::{Channels, Decoder};
use prost::Message;
use std::io::{ErrorKind, Read};
use std::net::SocketAddr;
use std::path::Path;
use tap_interface::tap::message::TapMessage;
pub fn record(addr: SocketAddr, output_path: &Path, duration_ms: u64) -> Result<(), Error> {
let mut decoder = Decoder::new(SAMPLE_RATE, Channels::Stereo)?;
let mut udp_connection = OpusUdpConnection::new(addr, addr)?;
let spec = hound::WavSpec {
channels: 2,
sample_rate: SAMPLE_RATE_RAW as u32,
bits_per_sample: 32,
sample_format: hound::SampleFormat::Float,
};
let mut seq_count = 0;
let mut writer = hound::WavWriter::create(output_path, spec)?;
let deadline = std::time::Instant::now() + std::time::Duration::from_millis(duration_ms);
while std::time::Instant::now() < deadline {
let mut packet = vec![0; TAP_PACKET_MAX_DATA_LEN];
let mut output_buffer = vec![0.0; STEREO_FRAME_SIZE];
let new_packet = match udp_connection.read(&mut packet) {
Ok(data_len) => Some(&packet[0..data_len]),
Err(err) => {
if err.kind() == ErrorKind::WouldBlock {
None
} else {
error!("Got error trying to recv from UDP: {err}");
continue;
}
}
};
let data_size = if let Some(new_packet) = new_packet {
let tap_message = TapMessage::decode(new_packet)?;
if tap_message.timestamp - Utc::now().timestamp() as u64 > 1 {
warn!(
"Packet outside of tolerance: packet.timestamp={} time={}",
tap_message.timestamp,
Utc::now().timestamp()
);
}
if tap_message.seq != seq_count {
warn!(
"Found sequence mismatch, expected seq={}, {tap_message:?}",
seq_count
);
}
(seq_count, _) = tap_message.seq.overflowing_add(1);
decoder.decode_float(&packet, &mut output_buffer, false)?
} else {
decoder.decode_float(&[], &mut output_buffer, false)?
} * 2;
for samples in output_buffer[..data_size].chunks(2) {
for sample in samples {
writer.write_sample(*sample)?;
}
}
}
writer.finalize()?;
Ok(())
}
-119
View File
@@ -1,119 +0,0 @@
use crate::tx_thread::TxThread;
use crate::udp_connection::OpusUdpConnection;
use crate::util::{SAMPLE_RATE, SAMPLE_RATE_RAW, STEREO_FRAME_SIZE, ThreadMessage};
use anyhow::Error;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{BufferSize, FromSample, Host, Sample, SampleRate, StreamConfig};
use log::{error, info};
use opus::{Application, Bitrate, Channels, Encoder, Signal};
use std::io::{Read, stdin};
use std::net::SocketAddr;
use std::sync::mpsc::{Sender, channel};
use std::thread;
pub fn tap(
device: &str,
src_addr: SocketAddr,
dest_addr: SocketAddr,
host: Host,
) -> Result<(), Error> {
// Set up the input device and stream with the default input config.
let device = if device == "default" {
host.default_input_device()
} else {
let id = &device.parse().expect("failed to parse input device id");
host.device_by_id(id)
}
.expect("failed to find input device");
for dev in host.input_devices()? {
info!("ID: {}", dev.id()?);
}
info!("Input device: {}", device.id()?);
let config = device
.default_input_config()
.expect("Failed to get default input config");
let mut stream_config = StreamConfig::from(config);
let count = stream_config.channels;
stream_config.sample_rate = SAMPLE_RATE_RAW as SampleRate;
stream_config.buffer_size = BufferSize::Fixed(STEREO_FRAME_SIZE as u32 / count as u32);
for cfg in device.supported_input_configs()? {
info!("{:?}", cfg)
}
info!("Stream config: {stream_config:?}");
let udp_connection = OpusUdpConnection::new(src_addr, dest_addr)?;
let mut encoder = Encoder::new(SAMPLE_RATE, Channels::Stereo, Application::Audio)?;
encoder.set_bitrate(Bitrate::Max)?;
encoder.set_complexity(10)?;
encoder.set_signal(Signal::Music)?;
let (sender, receiver) = channel();
let tx_thread = TxThread::new(receiver, encoder, udp_connection);
thread::spawn(move || tx_thread.thread());
let err_fn = move |err| {
error!("an error occurred on stream: {err}");
};
let stream = match config.sample_format() {
cpal::SampleFormat::I8 => device.build_input_stream(
stream_config,
move |data, _: &_| write_input_data::<i8>(data, &sender),
err_fn,
None,
)?,
cpal::SampleFormat::I16 => device.build_input_stream(
stream_config,
move |data, _: &_| write_input_data::<i16>(data, &sender),
err_fn,
None,
)?,
cpal::SampleFormat::I32 => device.build_input_stream(
stream_config,
move |data, _: &_| write_input_data::<i32>(data, &sender),
err_fn,
None,
)?,
cpal::SampleFormat::F32 => device.build_input_stream(
stream_config,
move |data, _: &_| write_input_data::<f32>(data, &sender),
err_fn,
None,
)?,
sample_format => {
return Err(anyhow::Error::msg(format!(
"Unsupported sample format '{sample_format}'"
)));
}
};
info!("Running tap...");
stream.play()?;
let mut stdin = stdin().lock();
let mut exit = [0u8; 1];
stdin.read_exact(&mut exit)?;
info!("Exiting...");
Ok(())
}
fn write_input_data<T>(input: &[T], ctx: &Sender<ThreadMessage>)
where
T: Sample,
f32: FromSample<T>,
{
let sample: Vec<f32> = input.iter().map(|s| f32::from_sample(*s)).collect();
if let Err(err) = ctx.send(ThreadMessage::Data(sample)) {
error!("Got error when sending data: {err}")
}
}
+22 -23
View File
@@ -1,14 +1,11 @@
mod audio_record;
mod audio_source;
mod tx_thread;
mod udp_connection;
mod util;
use crate::audio_record::record;
use crate::udp_connection::OpusUdpConnection;
use clap::{Parser, Subcommand};
use log::LevelFilter;
use log::{LevelFilter, error};
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::mpsc::channel;
use tap_core::audio_source_thread::AudioSourceThread;
#[derive(Parser, Debug)]
#[command(version, about = "Tap sounds from a audio device and send them over a UDP socket", long_about = None)]
@@ -29,18 +26,9 @@ enum Tasks {
/// UDP socket addr to connect to
dest: SocketAddr,
},
Record {
/// UDP addr to receive data on
addr: SocketAddr,
/// Number of MS to record for
duration_ms: u64,
/// Output file
output_file: PathBuf,
},
}
fn main() -> Result<(), anyhow::Error> {
fn main() {
env_logger::builder()
.filter_level(LevelFilter::Info)
.parse_default_env()
@@ -50,11 +38,22 @@ fn main() -> Result<(), anyhow::Error> {
let host = cpal::default_host();
match opt.command {
Tasks::Tap { device, src, dest } => audio_source::tap(&device, src, dest, host),
Tasks::Record {
addr,
duration_ms,
output_file,
} => record(addr, &output_file, duration_ms),
Tasks::Tap { device, src, dest } => {
let udp_connection = match OpusUdpConnection::new(src, dest) {
Ok(c) => c,
Err(err) => {
error!("Failed to open udp connection: {err}");
return;
}
};
let (_sender, receiver) = channel();
let audio_source_thread = AudioSourceThread::new(device, host, receiver);
if let Err(err) = audio_source_thread.worker(udp_connection) {
error!("Error running tap: {err}");
}
}
};
}
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "tap_core"
version = "0.1.0"
edition = "2024"
[dependencies]
cpal = {version = "0.18.2", features = ["pulseaudio"]}
chrono = "0.4.45"
circular-buffer = "2.0.1"
log = "0.4.27"
prost = "0.14.4"
tap-interface = {version = "0.1.0", registry = "ahines"}
thiserror = "2.0.21"
opus= { version="0.4.0" }
opusic-sys = { version = "0.7.5", features = ["bundled", "no-stack-protector", "no-hardening", "no-fortify-source"] }
+171
View File
@@ -0,0 +1,171 @@
use crate::tx_thread::TxThread;
use crate::util::{SAMPLE_RATE, SAMPLE_RATE_RAW, STEREO_FRAME_SIZE, ThreadMessage};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{BufferSize, FromSample, Host, Sample, SampleRate, Stream, StreamConfig};
use log::{debug, error, info};
use opus::{Application, Bitrate, Channels, Encoder, Signal};
use std::io::{Read, Write};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::thread;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("CPAL error: {0}")]
CpalError(#[from] cpal::Error),
#[error("Opus error: {0}")]
OpusError(#[from] opus::Error),
#[error("Unknown sample error format")]
UnknownSampleErrorFormat,
#[error("Unknown device")]
UnknownDevice,
}
#[derive(Debug)]
pub enum Commands {
SetDevice(String),
Pause,
Resume,
}
pub struct AudioSourceThread {
device: String,
host: Host,
cmd_channel: Receiver<Commands>,
}
impl AudioSourceThread {
pub fn new(device: String, host: Host, cmd_channel: Receiver<Commands>) -> Self {
Self {
device,
host,
cmd_channel,
}
}
fn setup_stream(&mut self, audio_sender: &Sender<ThreadMessage>) -> Result<Stream, Error> {
let device = if self.device == "default" {
self.host.default_input_device()
} else {
let id = &self.device.parse()?;
self.host.device_by_id(id)
};
let device = device.ok_or(Error::UnknownDevice)?;
info!("Input device: {}", device.id()?);
let config = device
.default_input_config()
.expect("Failed to get default input config");
let mut stream_config = StreamConfig::from(config);
let count = stream_config.channels;
stream_config.sample_rate = SAMPLE_RATE_RAW as SampleRate;
stream_config.buffer_size = BufferSize::Fixed(STEREO_FRAME_SIZE as u32 / count as u32);
info!("Stream config: {stream_config:?}");
for cfg in device.supported_input_configs()? {
debug!("{:?}", cfg)
}
let callback_sender = audio_sender.clone();
let stream = match config.sample_format() {
cpal::SampleFormat::I8 => device.build_input_stream(
stream_config,
move |data, _: &_| write_input_data::<i8>(data, &callback_sender),
err_fn,
None,
)?,
cpal::SampleFormat::I16 => device.build_input_stream(
stream_config,
move |data, _: &_| write_input_data::<i16>(data, &callback_sender),
err_fn,
None,
)?,
cpal::SampleFormat::I32 => device.build_input_stream(
stream_config,
move |data, _: &_| write_input_data::<i32>(data, &callback_sender),
err_fn,
None,
)?,
cpal::SampleFormat::F32 => device.build_input_stream(
stream_config,
move |data, _: &_| write_input_data::<f32>(data, &callback_sender),
err_fn,
None,
)?,
sample_format => {
error!("Unknown sample format: '{sample_format}'");
return Err(Error::UnknownSampleErrorFormat);
}
};
Ok(stream)
}
pub fn worker<T: Read + Write + Send + 'static>(mut self, connection: T) -> Result<(), Error> {
let encoder = Self::setup_encoder()?;
let (sender, receiver) = channel();
let tx_thread = TxThread::new(receiver, encoder, connection);
let thread = thread::spawn(move || tx_thread.thread());
let mut stream = self.setup_stream(&sender)?;
info!("Running...");
stream.play()?;
loop {
let msg = self.cmd_channel.recv();
if let Ok(msg) = msg {
match msg {
Commands::SetDevice(dev) => {
info!("Setting device to {dev}...");
self.device = dev;
drop(stream);
stream = self.setup_stream(&sender)?;
stream.play()?;
}
Commands::Pause => {
info!("Pausing Stream...");
stream.pause()?;
}
Commands::Resume => {
info!("Playing stream...");
stream.play()?;
}
}
} else {
error!("Channel closing, stopping threads");
drop(sender);
thread.join().unwrap();
return Ok(());
}
}
}
fn setup_encoder() -> Result<Encoder, Error> {
let mut encoder = Encoder::new(SAMPLE_RATE, Channels::Stereo, Application::Audio)?;
encoder.set_bitrate(Bitrate::Max)?;
encoder.set_complexity(10)?;
encoder.set_signal(Signal::Music)?;
Ok(encoder)
}
}
fn err_fn(error: cpal::Error) {
error!("Got an error processing audio data: {error}")
}
fn write_input_data<T>(input: &[T], ctx: &Sender<ThreadMessage>)
where
T: Sample,
f32: FromSample<T>,
{
let sample: Vec<f32> = input.iter().map(|s| f32::from_sample(*s)).collect();
if let Err(err) = ctx.send(ThreadMessage::Data(sample)) {
error!("Got error when sending data: {err}")
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod audio_source_thread;
pub mod tx_thread;
pub mod util;
+7 -12
View File
@@ -1,4 +1,3 @@
use crate::udp_connection::OpusUdpConnection;
use crate::util::STEREO_FRAME_SIZE;
use crate::util::{CIRC_BUFFER_SIZE, TAP_PACKET_MAX_DATA_LEN, ThreadMessage};
use chrono::Utc;
@@ -6,7 +5,7 @@ use circular_buffer::FixedCircularBuffer;
use log::{error, info, warn};
use opus::Encoder;
use prost::Message;
use std::io::Write;
use std::io::{Read, Write};
use std::sync::mpsc::{Receiver, RecvError};
use tap_interface::tap::message::TapMessage;
use thiserror::Error;
@@ -21,25 +20,21 @@ pub enum Error {
EncodeFailure(#[from] opus::Error),
}
pub struct TxThread {
pub struct TxThread<T: Read + Write> {
receiver: Receiver<ThreadMessage>,
encoder: Encoder,
opus_udp_connection: OpusUdpConnection,
opus_connection: T,
circular_buffer: FixedCircularBuffer<f32, CIRC_BUFFER_SIZE>,
seq_count: u32,
exit: bool,
}
impl TxThread {
pub fn new(
receiver: Receiver<ThreadMessage>,
encoder: Encoder,
opus_udp_connection: OpusUdpConnection,
) -> Self {
impl<T: Read + Write> TxThread<T> {
pub fn new(receiver: Receiver<ThreadMessage>, encoder: Encoder, opus_connection: T) -> Self {
Self {
receiver,
encoder,
opus_udp_connection,
opus_connection,
circular_buffer: Default::default(),
seq_count: 0,
exit: false,
@@ -63,7 +58,7 @@ impl TxThread {
audio_data: output_buffer,
};
self.opus_udp_connection
self.opus_connection
.write_all(tap_message.encode_to_vec().as_slice())?;
(self.seq_count, _) = self.seq_count.overflowing_add(1);
File renamed without changes.