tap beginnings, audio out. investigate samplerate bug

This commit is contained in:
TerraMaster85 2024-12-15 23:52:17 -05:00
parent 2094fa73bf
commit 7d76a0cd5b
9 changed files with 332 additions and 32 deletions

117
Cargo.lock generated
View file

@ -149,6 +149,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "clang-sys" name = "clang-sys"
version = "1.8.1" version = "1.8.1"
@ -225,12 +231,97 @@ version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "dasp_envelope"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ec617ce7016f101a87fe85ed44180839744265fae73bb4aa43e7ece1b7668b6"
dependencies = [
"dasp_frame",
"dasp_peak",
"dasp_ring_buffer",
"dasp_rms",
"dasp_sample",
]
[[package]]
name = "dasp_frame"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a3937f5fe2135702897535c8d4a5553f8b116f76c1529088797f2eee7c5cd6"
dependencies = [
"dasp_sample",
]
[[package]]
name = "dasp_interpolate"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc975a6563bb7ca7ec0a6c784ead49983a21c24835b0bc96eea11ee407c7486"
dependencies = [
"dasp_frame",
"dasp_ring_buffer",
"dasp_sample",
]
[[package]]
name = "dasp_peak"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cf88559d79c21f3d8523d91250c397f9a15b5fc72fbb3f87fdb0a37b79915bf"
dependencies = [
"dasp_frame",
"dasp_sample",
]
[[package]]
name = "dasp_ring_buffer"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07d79e19b89618a543c4adec9c5a347fe378a19041699b3278e616e387511ea1"
[[package]]
name = "dasp_rms"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6c5dcb30b7e5014486e2822537ea2beae50b19722ffe2ed7549ab03774575aa"
dependencies = [
"dasp_frame",
"dasp_ring_buffer",
"dasp_sample",
]
[[package]] [[package]]
name = "dasp_sample" name = "dasp_sample"
version = "0.11.0" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
[[package]]
name = "dasp_signal"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa1ab7d01689c6ed4eae3d38fe1cea08cba761573fbd2d592528d55b421077e7"
dependencies = [
"dasp_envelope",
"dasp_frame",
"dasp_interpolate",
"dasp_peak",
"dasp_ring_buffer",
"dasp_rms",
"dasp_sample",
"dasp_window",
]
[[package]]
name = "dasp_window"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99ded7b88821d2ce4e8b842c9f1c86ac911891ab89443cc1de750cae764c5076"
dependencies = [
"dasp_sample",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.13.0" version = "1.13.0"
@ -429,6 +520,18 @@ dependencies = [
"jni-sys", "jni-sys",
] ]
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.3" version = "7.1.3"
@ -677,9 +780,11 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cpal", "cpal",
"dasp_signal",
"ringbuf", "ringbuf",
"thiserror 2.0.6", "thiserror 2.0.6",
"tokio", "tokio",
"tokio-tun",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
] ]
@ -820,6 +925,18 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tokio-tun"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bf48df2abbfd12d0b8495dcf960b125109493d85f2c787b44a2729a8022bc4b"
dependencies = [
"libc",
"nix",
"thiserror 1.0.69",
"tokio",
]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.8" version = "0.6.8"

View file

@ -6,8 +6,10 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.94" anyhow = "1.0.94"
cpal = "0.15.3" cpal = "0.15.3"
dasp_signal = "0.11.0"
ringbuf = "0.4.7" ringbuf = "0.4.7"
thiserror = "2.0.6" thiserror = "2.0.6"
tokio = { version = '^1', features = ["full"] } tokio = { version = '^1', features = ["full"] }
tokio-tun = "0.12.1"
tracing = "0.1.41" tracing = "0.1.41"
tracing-subscriber = "0.3.19" tracing-subscriber = "0.3.19"

View file

@ -14,7 +14,9 @@ pub fn audio_receiver_main(
cpal_input: cpal::Device, cpal_input: cpal::Device,
) -> Result<()> { ) -> Result<()> {
trace!("audio_receiver started"); trace!("audio_receiver started");
let cpal_input_config = cpal_input.supported_input_configs()?.max_by_key(|cfg| { let cpal_input_config = cpal_input.supported_input_configs()
.context("could not find a suitable audio input configuration. are you root, trying to use a normal user's audio stack? try setting the capability CAP_NET_ADMIN=ep on this binary, and running as the user who is running the audio stack.")?
.max_by_key(|cfg| {
match cfg.sample_format() { match cfg.sample_format() {
SampleFormat::U8 => 10, SampleFormat::U8 => 10,
SampleFormat::I8 => 11, SampleFormat::I8 => 11,
@ -47,58 +49,58 @@ pub fn audio_receiver_main(
move |pcm, _: &_| take_input_pcm::<u8>(pcm, &tx_for_demod), move |pcm, _: &_| take_input_pcm::<u8>(pcm, &tx_for_demod),
failed_pcm_take, failed_pcm_take,
None, None,
)?, ).context("failed to build input stream")?,
cpal::SampleFormat::I8 => cpal_input.build_input_stream( cpal::SampleFormat::I8 => cpal_input.build_input_stream(
&cpal_input_config.into(), &cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<i8>(pcm, &tx_for_demod), move |pcm, _: &_| take_input_pcm::<i8>(pcm, &tx_for_demod),
failed_pcm_take, failed_pcm_take,
None, None,
)?, ).context("failed to build input stream")?,
cpal::SampleFormat::I16 => cpal_input.build_input_stream( cpal::SampleFormat::I16 => cpal_input.build_input_stream(
&cpal_input_config.into(), &cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<i16>(pcm, &tx_for_demod), move |pcm, _: &_| take_input_pcm::<i16>(pcm, &tx_for_demod),
failed_pcm_take, failed_pcm_take,
None, None,
)?, ).context("failed to build input stream")?,
cpal::SampleFormat::I32 => cpal_input.build_input_stream( cpal::SampleFormat::I32 => cpal_input.build_input_stream(
&cpal_input_config.into(), &cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<i32>(pcm, &tx_for_demod), move |pcm, _: &_| take_input_pcm::<i32>(pcm, &tx_for_demod),
failed_pcm_take, failed_pcm_take,
None, None,
)?, ).context("failed to build input stream")?,
cpal::SampleFormat::F32 => cpal_input.build_input_stream( cpal::SampleFormat::F32 => cpal_input.build_input_stream(
&cpal_input_config.into(), &cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<f32>(pcm, &tx_for_demod), move |pcm, _: &_| take_input_pcm::<f32>(pcm, &tx_for_demod),
failed_pcm_take, failed_pcm_take,
None, None,
)?, ).context("failed to build input stream")?,
cpal::SampleFormat::F64 => cpal_input.build_input_stream( cpal::SampleFormat::F64 => cpal_input.build_input_stream(
&cpal_input_config.into(), &cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<f64>(pcm, &tx_for_demod), move |pcm, _: &_| take_input_pcm::<f64>(pcm, &tx_for_demod),
failed_pcm_take, failed_pcm_take,
None, None,
)?, ).context("failed to build input stream")?,
cpal::SampleFormat::U64 => cpal_input.build_input_stream( cpal::SampleFormat::U64 => cpal_input.build_input_stream(
&cpal_input_config.into(), &cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<u64>(pcm, &tx_for_demod), move |pcm, _: &_| take_input_pcm::<u64>(pcm, &tx_for_demod),
failed_pcm_take, failed_pcm_take,
None, None,
)?, ).context("failed to build input stream")?,
cpal::SampleFormat::I64 => cpal_input.build_input_stream( cpal::SampleFormat::I64 => cpal_input.build_input_stream(
&cpal_input_config.into(), &cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<i64>(pcm, &tx_for_demod), move |pcm, _: &_| take_input_pcm::<i64>(pcm, &tx_for_demod),
failed_pcm_take, failed_pcm_take,
None, None,
)?, ).context("failed to build input stream")?,
sample_format => { sample_format => {
Err(anyhow::Error::msg( Err(anyhow::Error::msg(
format!( format!(
"unsupported sample format '{}'", sample_format) "unsupported sample format '{}'! please ask developers to add it", sample_format)
))? ))?
} }
}; };
input_stream.play()?; input_stream.play().context("failed to start intake audio stream")?;
Ok(()) Ok(())
} }

View file

@ -0,0 +1,148 @@
use anyhow::{Context, Result};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{FromSample, Sample, SampleFormat};
use dasp_signal::Signal;
use tracing::{error, info, trace, warn};
use tokio::sync::mpsc::Receiver;
use crate::task_msg::{PcmFrameMessage, PcmSampleMessage};
pub fn audio_transmitter_main(
mut rx_for_wire: Receiver<PcmSampleMessage>,
cpal_output: cpal::Device,
) -> Result<()> {
trace!("audio_transmitter started");
let cpal_output_config = cpal_output.supported_output_configs()
.context("could not find a suitable audio output configuration. are you root, trying to use a normal user's audio stack? try setting the capability CAP_NET_ADMIN=ep on this binary, and running as the user who is running the audio stack.")?
.max_by_key(|cfg| {
match cfg.sample_format() {
SampleFormat::U8 => 10,
SampleFormat::I8 => 11,
SampleFormat::U16 => 20,
SampleFormat::I16 => 21,
SampleFormat::U32 => 30,
SampleFormat::I32 => 31,
SampleFormat::F32 => 40,
SampleFormat::F64 => 50,
SampleFormat::U64 => 60,
SampleFormat::I64 => 61,
x => { warn!("Backend offered unknown sample format {x}",); -999 }
}
}).expect("output device has no supported configurations available")
.with_max_sample_rate();
info!(
"output samples are {} @ {}hz",
&cpal_output_config.sample_format(),
&cpal_output_config.sample_rate().0,
);
let failed_pcm_give = move |e| {
warn!("dropped some PCM data on output stream?! {}", e);
};
let mut i = 0f64;
let output_stream = match cpal_output_config.sample_format() {
cpal::SampleFormat::U8 => cpal_output
.build_output_stream(
&cpal_output_config.into(),
move |pcm, _: &_| give_output_pcm::<u8>(pcm, &mut rx_for_wire, &mut i),
failed_pcm_give,
None,
)
.context("failed to build output stream")?,
cpal::SampleFormat::I8 => cpal_output
.build_output_stream(
&cpal_output_config.into(),
move |pcm, _: &_| give_output_pcm::<i8>(pcm, &mut rx_for_wire, &mut i),
failed_pcm_give,
None,
)
.context("failed to build output stream")?,
cpal::SampleFormat::I16 => cpal_output
.build_output_stream(
&cpal_output_config.into(),
move |pcm, _: &_| give_output_pcm::<i16>(pcm, &mut rx_for_wire, &mut i),
failed_pcm_give,
None,
)
.context("failed to build output stream")?,
cpal::SampleFormat::I32 => cpal_output
.build_output_stream(
&cpal_output_config.into(),
move |pcm, _: &_| give_output_pcm::<i32>(pcm, &mut rx_for_wire, &mut i),
failed_pcm_give,
None,
)
.context("failed to build output stream")?,
cpal::SampleFormat::F32 => cpal_output
.build_output_stream(
&cpal_output_config.into(),
move |pcm, _: &_| give_output_pcm::<f32>(pcm, &mut rx_for_wire, &mut i),
failed_pcm_give,
None,
)
.context("failed to build output stream")?,
cpal::SampleFormat::F64 => cpal_output
.build_output_stream(
&cpal_output_config.into(),
move |pcm, _: &_| give_output_pcm::<f64>(pcm, &mut rx_for_wire, &mut i),
failed_pcm_give,
None,
)
.context("failed to build output stream")?,
cpal::SampleFormat::U64 => cpal_output
.build_output_stream(
&cpal_output_config.into(),
move |pcm, _: &_| give_output_pcm::<u64>(pcm, &mut rx_for_wire, &mut i),
failed_pcm_give,
None,
)
.context("failed to build output stream")?,
cpal::SampleFormat::I64 => cpal_output
.build_output_stream(
&cpal_output_config.into(),
move |pcm, _: &_| give_output_pcm::<i64>(pcm, &mut rx_for_wire, &mut i),
failed_pcm_give,
None,
)
.context("failed to build output stream")?,
sample_format => Err(anyhow::Error::msg(format!(
"unsupported sample format '{}'! please ask developers to add it",
sample_format
)))?,
};
output_stream
.play()
.context("failed to start ingive audio stream")?;
loop{}
Ok(())
}
fn give_output_pcm<T>(pcm: &mut [T], channel: &mut Receiver<PcmSampleMessage>, i: &mut f64)
where
T: Sample + std::fmt::Debug + FromSample<i64> + FromSample<f64>,
{
println!("wanted {} samples", pcm.len());
let mut signal = dasp_signal::rate(384000.0).const_hz(100.0).sine();
for outgoing in pcm.iter_mut() {
*outgoing = match channel.try_recv() {
Ok(sample) => sample.to_sample::<T>(),
//Err(_) => (std::f64::consts::PI * 2.0*(*i)/(384000.0/100.0)).sin().to_sample::<T>(), //T::EQUILIBRIUM,
Err(_) => {
signal.next().to_sample::<T>()
}
};
*i = *i % 384000.0 + 1.0;
//if *i > 200000.0 { break; }
}
//let mut ctr = 0usize;
}

View file

@ -4,10 +4,10 @@ use tracing::{error, info, trace, warn};
use tokio::sync::mpsc::{Receiver, UnboundedSender}; use tokio::sync::mpsc::{Receiver, UnboundedSender};
use crate::task_msg::{RawIpDataMessage, PcmFrameMessage}; use crate::task_msg::{RawEthFrameMessage, PcmFrameMessage};
pub async fn dsp_inb_main( pub async fn dsp_inb_main(
tx_for_ip_inb: UnboundedSender<RawIpDataMessage>, tx_for_eth_inb: UnboundedSender<RawEthFrameMessage>,
rx_for_demod: Receiver<PcmFrameMessage>, rx_for_demod: Receiver<PcmFrameMessage>,
) -> Result<()> { ) -> Result<()> {
trace!("pcm_inb task started"); trace!("pcm_inb task started");

View file

@ -14,15 +14,21 @@ use tokio::sync::mpsc::{channel, unbounded_channel};
//use ringbuf:: //use ringbuf::
mod task_msg;
use crate::task_msg::{PcmFrameMessage, RawIpDataMessage};
mod audio_receiver; mod audio_receiver;
use crate::audio_receiver::audio_receiver_main; use crate::audio_receiver::audio_receiver_main;
mod audio_transmitter;
use crate::audio_transmitter::audio_transmitter_main;
mod dsp_inb; mod dsp_inb;
use crate::dsp_inb::dsp_inb_main; use crate::dsp_inb::dsp_inb_main;
mod tap_junction;
use crate::tap_junction::tap_junction_main;
mod task_msg;
use crate::task_msg::{PcmFrameMessage, PcmSampleMessage, RawEthFrameMessage};
mod wire_fmt; mod wire_fmt;
//#[derive(argh::FromArgs)] //#[derive(argh::FromArgs)]
@ -39,14 +45,14 @@ async fn main() -> Result<()> {
"no audio input could be found; is a microphone installed?" "no audio input could be found; is a microphone installed?"
)?; )?;
let cpal_output = cpal_host.default_output_device().context( let cpal_output = cpal_host.default_output_device().context(
"no audio output could be found; is a sound card installed?", "no audio output could be found; is a sound card installed?"
)?; )?;
let (tx_for_demod, rx_for_demod) = channel::<PcmFrameMessage>(1024); let (tx_for_demod, rx_for_demod) = channel::<PcmFrameMessage>(1024);
let (tx_for_ip_inb, rx_for_ip_inb) = let (tx_for_eth_inb, rx_for_eth_inb) =
unbounded_channel::<RawIpDataMessage>(); unbounded_channel::<RawEthFrameMessage>();
let (tx_for_mod, rx_for_mod) = unbounded_channel::<RawIpDataMessage>(); let (tx_for_mod, rx_for_mod) = channel::<RawEthFrameMessage>(1024);
let (tx_for_wire, rx_for_wire) = unbounded_channel::<PcmFrameMessage>(); let (tx_for_wire, rx_for_wire) = channel::<PcmSampleMessage>(1);
trace!("starting listener for audio on wire"); trace!("starting listener for audio on wire");
audio_receiver_main(tx_for_demod, cpal_input) audio_receiver_main(tx_for_demod, cpal_input)
@ -56,20 +62,19 @@ async fn main() -> Result<()> {
let task_dsp_inb = tasks.spawn(async move { let task_dsp_inb = tasks.spawn(async move {
trace!("starting inbound audio demodulator"); trace!("starting inbound audio demodulator");
dsp_inb_main(tx_for_ip_inb, rx_for_demod).await dsp_inb_main(tx_for_eth_inb, rx_for_demod).await
});
let task_tap_junction = tasks.spawn(async move {
trace!("Starting tap manager");
tap_junction_main(tx_for_mod, rx_for_eth_inb).await
}); });
//let task_tun_junction = tasks.spawn(async move {
// trace!("Starting tun manager");
// tun_junction_main(tx_for_mod, rx_for_ip_inb).await
//});
//let task_dsp_outb = tasks.spawn(async move { //let task_dsp_outb = tasks.spawn(async move {
// trace!("Starting outbound audio modulator"); // trace!("Starting outbound audio modulator");
// dsp_outb_main(tx_for_wire, rx_for_mod).await // dsp_outb_main(tx_for_wire, rx_for_mod).await
//}); //});
//let task_audio_sender = tasks.spawn(async move { trace!("Starting audio sender");
// trace!("Starting audio transmitter"); audio_transmitter_main(rx_for_wire, cpal_output)
// audio_sender_main(rx_for_mod, cpal_output).await .context("transmitter for audio on wire has failed to start")?;
//});
while let Some(task_result) = tasks.join_next().await { while let Some(task_result) = tasks.join_next().await {
task_result?? task_result??

25
src/tap_junction/mod.rs Normal file
View file

@ -0,0 +1,25 @@
use anyhow::{Context, Result};
use crate::task_msg::RawEthFrameMessage;
pub async fn tap_junction_main(
tx_for_mod: tokio::sync::mpsc::Sender<RawEthFrameMessage>,
rx_for_eth_inb: tokio::sync::mpsc::UnboundedReceiver<RawEthFrameMessage>,
) -> Result<()> {
const MTU: i32 = 1500;
let tap_dev = tokio_tun::Tun::builder()
.tap()
.name("sang")
.mtu(MTU)
.address("10.185.0.1".parse()?)
.netmask("255.255.255.0".parse()?)
.up()
.try_build()
.context("failed to construct tap device! could this be a permissions issue? try `setcap CAP_NET_ADMIN=ep /path/to/binary`")?;
let mut buf = [0u8; MTU as usize];
loop {
let frame_length = tap_dev.recv(&mut buf).await?;
println!("{:?}", &buf[..frame_length]);
}
}

View file

@ -1,2 +1,3 @@
pub type PcmFrameMessage = Vec<i64>; pub type PcmFrameMessage = Vec<i64>;
pub type RawIpDataMessage = Vec<u8>; pub type PcmSampleMessage = i64;
pub type RawEthFrameMessage = Vec<u8>;

View file

@ -3,7 +3,7 @@ pub struct Capabilities {
} }
pub enum Service { pub enum Service {
elephone = 1, Telephone = 1,
Typewriter = 2, Typewriter = 2,
IpDce = 3, IpDce = 3,
EthernetDce = 4, EthernetDce = 4,