diff options
| author | LLLL Colonq <llll@colonq> | 2025-10-23 18:22:44 -0400 |
|---|---|---|
| committer | LLLL Colonq <llll@colonq> | 2025-10-23 18:22:44 -0400 |
| commit | 99a9b8c0bcc345f7044ab18840e78ee9263cae9b (patch) | |
| tree | 5177a48645a405b70ec39a34dc16a7fdb044ec63 /crates/renderer | |
| parent | 4ae21efc81f39ca805cefe5c1af722857f22f255 (diff) | |
Add audio loopback overlay
Diffstat (limited to 'crates/renderer')
| -rw-r--r-- | crates/renderer/Cargo.toml | 4 | ||||
| -rw-r--r-- | crates/renderer/src/main.rs | 1 | ||||
| -rw-r--r-- | crates/renderer/src/overlay.rs | 1 | ||||
| -rw-r--r-- | crates/renderer/src/overlay/automata.rs | 1 | ||||
| -rw-r--r-- | crates/renderer/src/overlay/loopback.rs | 87 |
5 files changed, 93 insertions, 1 deletions
diff --git a/crates/renderer/Cargo.toml b/crates/renderer/Cargo.toml index e66e198..6bf77a7 100644 --- a/crates/renderer/Cargo.toml +++ b/crates/renderer/Cargo.toml @@ -26,4 +26,6 @@ polling = "*" # polling sockets termion = "*" # terminal escapes device_query = "*" # get pressed keys when unfocused byteorder = "*" # read little-endian numbers -image = "*" # read and write image files
\ No newline at end of file +image = "*" # read and write image files +cpal = "*" # record microphone +redis = "*" # database
\ No newline at end of file diff --git a/crates/renderer/src/main.rs b/crates/renderer/src/main.rs index 1e16092..b6103fc 100644 --- a/crates/renderer/src/main.rs +++ b/crates/renderer/src/main.rs @@ -32,6 +32,7 @@ pub fn main() -> Erm<()> { Box::new(overlay::drawing::Overlay::new(ctx)), Box::new(overlay::irish::Overlay::new(ctx)), // Box::new(overlay::model::Overlay::new(ctx)), + Box::new(overlay::loopback::Overlay::new(ctx)), ]) })?; }, diff --git a/crates/renderer/src/overlay.rs b/crates/renderer/src/overlay.rs index 9d1f722..38e75c5 100644 --- a/crates/renderer/src/overlay.rs +++ b/crates/renderer/src/overlay.rs @@ -3,6 +3,7 @@ pub mod shader; pub mod drawing; pub mod automata; pub mod irish; +pub mod loopback; use teleia::*; diff --git a/crates/renderer/src/overlay/automata.rs b/crates/renderer/src/overlay/automata.rs index 22f7884..fbbc138 100644 --- a/crates/renderer/src/overlay/automata.rs +++ b/crates/renderer/src/overlay/automata.rs @@ -115,6 +115,7 @@ impl CellBuffer { pub fn get(&self, x: i32, y: i32) -> Cell { self.buf[Self::idx(x, y)] } + pub fn neighbors(&self, x: i32, y: i32) -> [Cell; 8] { [ self.get(x-1, y-1), self.get(x, y-1), diff --git a/crates/renderer/src/overlay/loopback.rs b/crates/renderer/src/overlay/loopback.rs new file mode 100644 index 0000000..b026eb3 --- /dev/null +++ b/crates/renderer/src/overlay/loopback.rs @@ -0,0 +1,87 @@ +use redis::Commands; +use teleia::*; +use std::{io::{Read, Write}, process}; +use byteorder::WriteBytesExt; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; + +use crate::overlay; + +fn ffmpeg_to_adts(sample_rate: u32, samples: &[f32]) -> Option<Vec<u8>> { + let proc = process::Command::new("ffmpeg") + .args([ + "-f", "f32le", + "-ar", &format!("{sample_rate}"), + "-ac", "2", + "-i", "pipe:0", + "-vn", + "-c:a", "aac", + "-f", "mpegts", + "pipe:1" + ]) + .stdin(process::Stdio::piped()) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::null()) + .spawn().ok()?; + { + let mut inp = proc.stdin?; + for s in samples { + inp.write_f32::<byteorder::LE>(*s).ok()?; + } + inp.flush().ok()?; + } + let mut out = proc.stdout?; + let mut ret = Vec::new(); + out.read_to_end(&mut ret).ok()?; + Some(ret) +} + +fn upload_sample(conn: &mut redis::Connection, sequence: u32, sample_rate: u32, sample: &[f32]) { + let max: f32 = *sample.iter().max_by(|x, y| f32::total_cmp(x, y)).unwrap(); + let cells = (max / 0.1) as usize; + let adts = ffmpeg_to_adts(sample_rate, sample).unwrap(); + println!("{} {} {}", sample.len(), adts.len(), "#".repeat(cells)); + let _: () = conn.lpush("hlssamples", adts).unwrap(); + let _: () = conn.ltrim("hlssamples", 0, 10).unwrap(); + let _: () = conn.set("hlssequence", sequence).unwrap(); +} + +pub struct Overlay { + stream: cpal::Stream, +} + +impl Overlay { + pub fn new(ctx: &context::Context) -> Self { + let redis = redis::Client::open("redis://shiro").unwrap(); + let mut redis_conn = redis.get_connection().unwrap(); + let host = cpal::default_host(); + let device = host.default_input_device().unwrap(); + let config = device.default_input_config().unwrap(); + let sample_rate = config.sample_rate().0; + let mut buf: Vec<f32> = Vec::new(); + let mut sequence = 0; + let _: () = redis_conn.del("hlssamples").unwrap(); + let _: () = redis_conn.set("hlssequence", 0).unwrap(); + let stream = device.build_input_stream( + &config.into(), + move |samples: &[f32], info| { + buf.extend_from_slice(samples); + let upload_size = (3 * 2 * sample_rate) as usize; + if buf.len() > upload_size { + upload_sample(&mut redis_conn, sequence, sample_rate, &buf[0..upload_size]); + buf.drain(0..upload_size); + sequence += 1; + } + }, + |err| { + println!("error: {}", err); + }, + None, + ).unwrap(); + stream.play().unwrap(); + Self { + stream, + } + } +} + +impl overlay::Overlay for Overlay {} |
