beginnings of prog architecture
This commit is contained in:
commit
c47228e4d9
9 changed files with 1492 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
2
.rustfmt.toml
Normal file
2
.rustfmt.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
max_width = 80
|
||||||
|
edition = "2024"
|
1265
Cargo.lock
generated
Normal file
1265
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "sang"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.94"
|
||||||
|
cpal = "0.15.3"
|
||||||
|
log = "0.4.22"
|
||||||
|
ringbuf = "0.4.7"
|
||||||
|
simple_logger = { version = "^5.0.0", features = ["colors", "threads", "timestamps"] }
|
||||||
|
thiserror = "2.0.6"
|
||||||
|
tokio = { version = '^1', features = ["full"] }
|
110
src/audio_receiver/mod.rs
Normal file
110
src/audio_receiver/mod.rs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
|
use cpal::{FromSample, Sample, SampleFormat};
|
||||||
|
|
||||||
|
use log::{error, info, trace, warn};
|
||||||
|
|
||||||
|
use tokio::sync::mpsc::Sender;
|
||||||
|
|
||||||
|
use crate::msg::PcmFrameMessage;
|
||||||
|
|
||||||
|
pub fn audio_receiver_main(
|
||||||
|
tx_for_demod: Sender<PcmFrameMessage>,
|
||||||
|
cpal_input: cpal::Device,
|
||||||
|
) -> Result<()> {
|
||||||
|
trace!("audio_receiver started");
|
||||||
|
let cpal_input_config = cpal_input.supported_input_configs()?.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("input device has no supported configurations available")
|
||||||
|
.with_max_sample_rate();
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"input samples are {} @ {}hz",
|
||||||
|
&cpal_input_config.sample_format(),
|
||||||
|
&cpal_input_config.sample_rate().0,
|
||||||
|
);
|
||||||
|
|
||||||
|
let failed_pcm_take = move |e| {
|
||||||
|
warn!("dropped some PCM data on input stream?! {}", e);
|
||||||
|
};
|
||||||
|
|
||||||
|
let input_stream = match cpal_input_config.sample_format() {
|
||||||
|
cpal::SampleFormat::U8 => cpal_input.build_input_stream(
|
||||||
|
&cpal_input_config.into(),
|
||||||
|
move |pcm, _: &_| take_input_pcm::<u8>(pcm, &tx_for_demod),
|
||||||
|
failed_pcm_take,
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
cpal::SampleFormat::I8 => cpal_input.build_input_stream(
|
||||||
|
&cpal_input_config.into(),
|
||||||
|
move |pcm, _: &_| take_input_pcm::<i8>(pcm, &tx_for_demod),
|
||||||
|
failed_pcm_take,
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
cpal::SampleFormat::I16 => cpal_input.build_input_stream(
|
||||||
|
&cpal_input_config.into(),
|
||||||
|
move |pcm, _: &_| take_input_pcm::<i16>(pcm, &tx_for_demod),
|
||||||
|
failed_pcm_take,
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
cpal::SampleFormat::I32 => cpal_input.build_input_stream(
|
||||||
|
&cpal_input_config.into(),
|
||||||
|
move |pcm, _: &_| take_input_pcm::<i32>(pcm, &tx_for_demod),
|
||||||
|
failed_pcm_take,
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
cpal::SampleFormat::F32 => cpal_input.build_input_stream(
|
||||||
|
&cpal_input_config.into(),
|
||||||
|
move |pcm, _: &_| take_input_pcm::<f32>(pcm, &tx_for_demod),
|
||||||
|
failed_pcm_take,
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
cpal::SampleFormat::F64 => cpal_input.build_input_stream(
|
||||||
|
&cpal_input_config.into(),
|
||||||
|
move |pcm, _: &_| take_input_pcm::<f64>(pcm, &tx_for_demod),
|
||||||
|
failed_pcm_take,
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
cpal::SampleFormat::U64 => cpal_input.build_input_stream(
|
||||||
|
&cpal_input_config.into(),
|
||||||
|
move |pcm, _: &_| take_input_pcm::<u64>(pcm, &tx_for_demod),
|
||||||
|
failed_pcm_take,
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
cpal::SampleFormat::I64 => cpal_input.build_input_stream(
|
||||||
|
&cpal_input_config.into(),
|
||||||
|
move |pcm, _: &_| take_input_pcm::<i64>(pcm, &tx_for_demod),
|
||||||
|
failed_pcm_take,
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
sample_format => {
|
||||||
|
Err(anyhow::Error::msg(
|
||||||
|
format!(
|
||||||
|
"unsupported sample format '{}'", sample_format)
|
||||||
|
))?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
input_stream.play()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take_input_pcm<T>(pcm: &[T], channel: &Sender<PcmFrameMessage>) where T: Sample + std::fmt::Debug, i64: FromSample<T> {
|
||||||
|
if channel.try_send(pcm.iter().map(|&n| n.to_sample::<i64>()).collect()).is_err() {
|
||||||
|
error!("demodulator PCM data queue is full???");
|
||||||
|
}
|
||||||
|
}
|
16
src/dsp_inb/mod.rs
Normal file
16
src/dsp_inb/mod.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
|
use log::{error, info, trace, warn};
|
||||||
|
|
||||||
|
use tokio::sync::mpsc::{Receiver, UnboundedSender};
|
||||||
|
|
||||||
|
use crate::msg::{RawIpDataMessage, PcmFrameMessage};
|
||||||
|
|
||||||
|
pub async fn dsp_inb_main(
|
||||||
|
tx_for_ip_inb: UnboundedSender<RawIpDataMessage>,
|
||||||
|
rx_for_demod: Receiver<PcmFrameMessage>,
|
||||||
|
) -> Result<()> {
|
||||||
|
trace!("pcm_inb task started");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
76
src/main.rs
Normal file
76
src/main.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#![cfg_attr(
|
||||||
|
debug_assertions,
|
||||||
|
allow(dead_code, unused_imports, unused_variables)
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
|
use cpal::traits::HostTrait;
|
||||||
|
|
||||||
|
use log::{trace};
|
||||||
|
|
||||||
|
use tokio::sync::mpsc::{channel, unbounded_channel};
|
||||||
|
|
||||||
|
//use ringbuf::
|
||||||
|
|
||||||
|
mod msg;
|
||||||
|
use crate::msg::{PcmFrameMessage, RawIpDataMessage};
|
||||||
|
|
||||||
|
mod audio_receiver;
|
||||||
|
use crate::audio_receiver::audio_receiver_main;
|
||||||
|
|
||||||
|
mod dsp_inb;
|
||||||
|
use crate::dsp_inb::dsp_inb_main;
|
||||||
|
|
||||||
|
//#[derive(argh::FromArgs)]
|
||||||
|
//struct Cli {}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
simple_logger::init_with_level(log::Level::Trace)
|
||||||
|
.expect("logger init failure???");
|
||||||
|
|
||||||
|
let cpal_host = cpal::default_host();
|
||||||
|
let cpal_input = cpal_host.default_input_device().context(
|
||||||
|
"no audio input could be found; is a microphone installed?"
|
||||||
|
)?;
|
||||||
|
let cpal_output = cpal_host.default_output_device().context(
|
||||||
|
"no audio output could be found; is a sound card installed?",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let (tx_for_demod, rx_for_demod) = channel::<PcmFrameMessage>(1024);
|
||||||
|
let (tx_for_ip_inb, rx_for_ip_inb) =
|
||||||
|
unbounded_channel::<RawIpDataMessage>();
|
||||||
|
let (tx_for_mod, rx_for_mod) = unbounded_channel::<RawIpDataMessage>();
|
||||||
|
let (tx_for_wire, rx_for_wire) = unbounded_channel::<PcmFrameMessage>();
|
||||||
|
|
||||||
|
trace!("starting listener for audio on wire");
|
||||||
|
audio_receiver_main(tx_for_demod, cpal_input)
|
||||||
|
.context("listener for audio on wire has failed to start");
|
||||||
|
|
||||||
|
let mut tasks = tokio::task::JoinSet::new();
|
||||||
|
|
||||||
|
let task_dsp_inb = tasks.spawn(async move {
|
||||||
|
trace!("starting inbound audio demodulator");
|
||||||
|
dsp_inb_main(tx_for_ip_inb, rx_for_demod).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 {
|
||||||
|
// trace!("Starting outbound audio modulator");
|
||||||
|
// dsp_outb_main(tx_for_wire, rx_for_mod).await
|
||||||
|
//});
|
||||||
|
//let task_audio_sender = tasks.spawn(async move {
|
||||||
|
// trace!("Starting audio transmitter");
|
||||||
|
// audio_sender_main(rx_for_mod, cpal_output).await
|
||||||
|
//});
|
||||||
|
|
||||||
|
while let Some(task_result) = tasks.join_next().await {
|
||||||
|
task_result??
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
2
src/msg.rs
Normal file
2
src/msg.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub type PcmFrameMessage = Vec<i64>;
|
||||||
|
pub type RawIpDataMessage = Vec<u8>;
|
7
src/wire-fmt/mod.rs
Normal file
7
src/wire-fmt/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
pub enum SpecialCapability {
|
||||||
|
pub StereoOut = 1,
|
||||||
|
pub StereoIn = 2,
|
||||||
|
pub Telephone = 3,
|
||||||
|
pub Typewriter = 4,
|
||||||
|
pub IpDce = 5,
|
||||||
|
}
|
Loading…
Reference in a new issue