commit f5c4ff8c352004f0982ff54c277f27a5954b84ac Author: Michael Sippel Date: Tue Dec 3 15:41:08 2024 +0100 add sine example diff --git a/01-play-sine/.gitignore b/01-play-sine/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/01-play-sine/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/01-play-sine/Cargo.toml b/01-play-sine/Cargo.toml new file mode 100644 index 0000000..23c88f9 --- /dev/null +++ b/01-play-sine/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "play-sine" +version = "0.1.0" +edition = "2021" + +[dependencies] +pipewire = "0.8.0" diff --git a/01-play-sine/src/main.rs b/01-play-sine/src/main.rs new file mode 100644 index 0000000..6ede0bf --- /dev/null +++ b/01-play-sine/src/main.rs @@ -0,0 +1,124 @@ +use std::io::Cursor; + +use pipewire::{ + context::Context, + main_loop::MainLoop, + properties::properties, + spa::{ + param::audio::{AudioFormat, AudioInfoRaw}, + pod::{serialize::PodSerializer, Object, Pod, Value}, + sys::{SPA_PARAM_EnumFormat, SPA_TYPE_OBJECT_Format}, + utils::Direction, + }, + stream::{Stream, StreamFlags}, +}; + +struct MyData { + time: u64, +} + +fn main() -> Result<(), Box> { + let mainloop = MainLoop::new(None)?; + let context = Context::new(&mainloop)?; + let core = context.connect(None)?; + let registry = core.get_registry()?; + + let _global_listener = registry + .add_listener_local() + .global(|global| println!("New global: {:?}", global)) + .register(); + + let sample_rate = 48000; + let channel_count = 2; + + let mut audio_info = AudioInfoRaw::new(); + audio_info.set_format(AudioFormat::F32LE); + audio_info.set_rate(sample_rate); + audio_info.set_channels(channel_count); + + let param_bytes = PodSerializer::serialize( + Cursor::new(vec![]), + &Value::Object(Object { + type_: SPA_TYPE_OBJECT_Format, + id: SPA_PARAM_EnumFormat, + properties: audio_info.into(), + }), + ) + .expect("failed to serialize param") + .0 + .into_inner(); + + let mut params = vec![Pod::from_bytes(¶m_bytes).expect("failed to create pod")]; + + let out_stream = Stream::new( + &core, + "out1", + properties! { + *pipewire::keys::MEDIA_TYPE => "Audio", + *pipewire::keys::MEDIA_CATEGORY => "Playback", + *pipewire::keys::MEDIA_ROLE => "Music" + }, + ) + .expect("failed to create stream"); + + out_stream + .connect( + Direction::Output, + None, + StreamFlags::AUTOCONNECT | StreamFlags::MAP_BUFFERS | StreamFlags::RT_PROCESS, + params.as_mut_slice(), + ) + .expect("failed to connect stream"); + + let my_data = MyData { time: 0 }; + + let _out_stream_listener = out_stream + .add_local_listener_with_user_data(my_data) + .process(move |stream, my_data: &mut MyData| { + let mut buffer = match stream.dequeue_buffer() { + Some(buf) => buf, + None => { + eprintln!("stream is out of buffers"); + return; + } + }; + + let stride = std::mem::size_of::() * (channel_count as usize); + let datas = buffer.datas_mut(); + + let data = &mut datas[0]; + + let mut samples_written = 0; + if let Some(slice) = data.data() { + let slice = unsafe { &mut *(slice as *mut _ as *mut [f32]) }; + + for i in 0..(slice.len() / stride) { + let time_samples = { + let t = my_data.time; + my_data.time += 1; + t + }; + let time_secs = (time_samples as f32) / 48000.0 as f32; + let sine_freq = 440.0; + let pi2 = 2.0 * 3.141; + let phi = pi2 * time_secs * sine_freq; + let value = 0.5 + 0.5 * f32::sin(phi); + + slice[2 * i] = value; + slice[2 * i + 1] = value; + samples_written += 2; + } + } + + let chunk = data.chunk_mut(); + *chunk.offset_mut() = 0; + *chunk.stride_mut() = stride as _; + *chunk.size_mut() = (stride * samples_written) as _; + }) + .register() + .expect("failed to add listener"); + + mainloop.run(); + + Ok(()) +}