Init and test sending samples to pipewire stream
This commit is contained in:
parent
f2cea03e27
commit
2c66db4205
4 changed files with 129 additions and 38 deletions
|
@ -8,6 +8,8 @@ use std::{
|
||||||
|
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
|
|
||||||
|
use pipewire::spa::pod::Pod;
|
||||||
|
|
||||||
use crate::{config::Settings, player::PlayerEvent, utils::Error};
|
use crate::{config::Settings, player::PlayerEvent, utils::Error};
|
||||||
|
|
||||||
pub enum AudioEvent {
|
pub enum AudioEvent {
|
||||||
|
@ -19,6 +21,8 @@ pub struct SoundManager {
|
||||||
error_chan: Sender<Error>,
|
error_chan: Sender<Error>,
|
||||||
audio_controls: Receiver<AudioEvent>,
|
audio_controls: Receiver<AudioEvent>,
|
||||||
player_chan: Sender<PlayerEvent>,
|
player_chan: Sender<PlayerEvent>,
|
||||||
|
sample_in: Sender<Vec<u8>>,
|
||||||
|
pw_signal_in: pipewire::channel::Sender<Box<Vec<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
|
@ -53,5 +57,5 @@ pub fn init(
|
||||||
Ok(audio_control_in)
|
Ok(audio_control_in)
|
||||||
}
|
}
|
||||||
|
|
||||||
mod pipewire;
|
mod pw;
|
||||||
mod sound_mgr;
|
mod sound_mgr;
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
use pipewire::{
|
|
||||||
context::Context, core::Core, keys, main_loop::MainLoop, properties::properties, stream::Stream,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::utils::Error;
|
|
||||||
|
|
||||||
pub struct PipewireContext {
|
|
||||||
pub mainloop: MainLoop,
|
|
||||||
context: Context,
|
|
||||||
core: Core,
|
|
||||||
stream: Stream,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init() -> Result<PipewireContext, Error> {
|
|
||||||
let mainloop = MainLoop::new(None)?;
|
|
||||||
let context = Context::new(&mainloop)?;
|
|
||||||
let core = context.connect(None)?;
|
|
||||||
let props = properties! {
|
|
||||||
*keys::MEDIA_TYPE => "Audio",
|
|
||||||
*keys::MEDIA_ROLE => "Music",
|
|
||||||
*keys::MEDIA_CATEGORY => "Playback",
|
|
||||||
*keys::AUDIO_CHANNELS => "2",
|
|
||||||
};
|
|
||||||
let audio_source = Stream::new(&core, "Subtails", props)?;
|
|
||||||
|
|
||||||
Ok(PipewireContext {
|
|
||||||
mainloop,
|
|
||||||
context,
|
|
||||||
core,
|
|
||||||
stream: audio_source,
|
|
||||||
})
|
|
||||||
}
|
|
76
src/audio/pw.rs
Normal file
76
src/audio/pw.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use std::{process::exit, sync::Arc};
|
||||||
|
|
||||||
|
use pipewire::{
|
||||||
|
channel::Receiver,
|
||||||
|
context::Context,
|
||||||
|
core::Core,
|
||||||
|
keys,
|
||||||
|
main_loop::MainLoop,
|
||||||
|
properties::properties,
|
||||||
|
spa::{pod::Pod, utils::Direction},
|
||||||
|
stream::{Stream, StreamFlags},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::utils::Error;
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
samples: std::sync::mpsc::Receiver<Vec<u8>>,
|
||||||
|
pw_signal: Receiver<Box<Vec<u8>>>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mainloop = MainLoop::new(None)?;
|
||||||
|
let context = Context::new(&mainloop)?;
|
||||||
|
let core = context.connect(None)?;
|
||||||
|
let props = properties! {
|
||||||
|
*keys::MEDIA_TYPE => "Audio",
|
||||||
|
*keys::MEDIA_ROLE => "Music",
|
||||||
|
*keys::MEDIA_CATEGORY => "Playback",
|
||||||
|
*keys::AUDIO_CHANNELS => "2",
|
||||||
|
};
|
||||||
|
let audio_source = Arc::new(Stream::new(&core, "Subtails", props)?);
|
||||||
|
|
||||||
|
let _listener = audio_source
|
||||||
|
.add_local_listener_with_user_data(0.0 as f64)
|
||||||
|
.process(move |stream, _| match stream.dequeue_buffer() {
|
||||||
|
None => {
|
||||||
|
eprintln!("Buffer dequeue failed!");
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
Some(mut buf) => {
|
||||||
|
let data = &mut (buf.datas_mut()[0]);
|
||||||
|
let n_iter = if let Some(sample_buf) = data.data() {
|
||||||
|
let next_samples = samples
|
||||||
|
.recv()
|
||||||
|
.expect("Sample sender disconnected unexpectedly");
|
||||||
|
if next_samples.len() > sample_buf.len() {
|
||||||
|
panic!("Buffer too small for given sample chunks");
|
||||||
|
}
|
||||||
|
let slice = &mut sample_buf[0..next_samples.len()];
|
||||||
|
slice.copy_from_slice(&next_samples);
|
||||||
|
slice.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let chunk = data.chunk_mut();
|
||||||
|
*chunk.offset_mut() = 0;
|
||||||
|
*chunk.size_mut() = n_iter as _;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.register()?;
|
||||||
|
|
||||||
|
let audio_source_ref = audio_source.clone();
|
||||||
|
let _receiver = pw_signal.attach(mainloop.loop_(), move |pod_bytes| {
|
||||||
|
let mut params = [Pod::from_bytes(&pod_bytes).unwrap()];
|
||||||
|
audio_source_ref.disconnect().unwrap();
|
||||||
|
audio_source_ref
|
||||||
|
.connect(
|
||||||
|
Direction::Output,
|
||||||
|
None,
|
||||||
|
StreamFlags::AUTOCONNECT | StreamFlags::MAP_BUFFERS | StreamFlags::ALLOC_BUFFERS,
|
||||||
|
&mut params,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
mainloop.run();
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,14 +1,24 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
f64::consts,
|
||||||
|
io::Cursor,
|
||||||
sync::{
|
sync::{
|
||||||
mpsc::{Receiver, Sender},
|
mpsc::{channel, Receiver, Sender},
|
||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use pipewire::spa::{
|
||||||
|
param::audio::{AudioFormat, AudioInfoRaw, MAX_CHANNELS},
|
||||||
|
pod::{serialize::PodSerializer, Object, Pod, Value},
|
||||||
|
sys::{
|
||||||
|
SPA_PARAM_EnumFormat, SPA_TYPE_OBJECT_Format, SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{config::Settings, player::PlayerEvent, utils::Error};
|
use crate::{config::Settings, player::PlayerEvent, utils::Error};
|
||||||
|
|
||||||
use super::{pipewire, AudioEvent, SoundManager};
|
use super::{pw, AudioEvent, SoundManager};
|
||||||
|
|
||||||
impl SoundManager {
|
impl SoundManager {
|
||||||
pub fn init(
|
pub fn init(
|
||||||
|
@ -18,30 +28,63 @@ impl SoundManager {
|
||||||
player_chan: Sender<PlayerEvent>,
|
player_chan: Sender<PlayerEvent>,
|
||||||
) -> Result<SoundManager, Error> {
|
) -> Result<SoundManager, Error> {
|
||||||
let error_chan_clone = error_chan.clone();
|
let error_chan_clone = error_chan.clone();
|
||||||
|
let (sample_in, sample_out) = channel();
|
||||||
|
let (pw_signal_in, pw_signal_out) = pipewire::channel::channel();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let pwire_context = match pipewire::init() {
|
match pw::init(sample_out, pw_signal_out) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error_chan_clone.send(err).unwrap();
|
error_chan_clone.send(err).unwrap();
|
||||||
thread::park();
|
thread::park();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ok(x) => x,
|
Ok(_) => (),
|
||||||
};
|
};
|
||||||
pwire_context.mainloop.run();
|
|
||||||
});
|
});
|
||||||
|
let mut test_info = AudioInfoRaw::new();
|
||||||
|
test_info.set_format(AudioFormat::S16LE);
|
||||||
|
test_info.set_rate(44100);
|
||||||
|
test_info.set_channels(2);
|
||||||
|
let mut positions = [0; MAX_CHANNELS];
|
||||||
|
(positions[0], positions[1]) = (SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR);
|
||||||
|
test_info.set_position(positions);
|
||||||
|
let test_pod = PodSerializer::serialize(
|
||||||
|
Cursor::new(Vec::new()),
|
||||||
|
&Value::Object(Object {
|
||||||
|
type_: SPA_TYPE_OBJECT_Format,
|
||||||
|
id: SPA_PARAM_EnumFormat,
|
||||||
|
properties: test_info.into(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.into_inner();
|
||||||
|
pw_signal_in.send(Box::new(test_pod)).unwrap();
|
||||||
Ok(SoundManager {
|
Ok(SoundManager {
|
||||||
settings,
|
settings,
|
||||||
error_chan,
|
error_chan,
|
||||||
audio_controls: audio_control_chan,
|
audio_controls: audio_control_chan,
|
||||||
player_chan,
|
player_chan,
|
||||||
|
sample_in,
|
||||||
|
pw_signal_in,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn begin(&mut self) -> Result<(), Error> {
|
pub fn begin(&mut self) -> Result<(), Error> {
|
||||||
|
let mut test_tone: Vec<u8> = vec![0; 1764];
|
||||||
|
for i in 0..441 {
|
||||||
|
let t = i as f64 / 441.0 * consts::PI * 2.0;
|
||||||
|
let v = (f64::sin(t) * 16767.0) as i16;
|
||||||
|
test_tone[i * 4..i * 4 + 2].copy_from_slice(&i16::to_le_bytes(v));
|
||||||
|
test_tone[i * 4 + 2..i * 4 + 4].copy_from_slice(&i16::to_le_bytes(v));
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match self.audio_controls.recv()? {
|
match self.audio_controls.recv()? {
|
||||||
AudioEvent::DecodeChunk(chunk) => (),
|
AudioEvent::DecodeChunk(chunk) => (),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
|
for i in 0..100 {
|
||||||
|
self.sample_in.send(test_tone.clone())?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue