diff --git a/qemu-display-listener/src/audio.rs b/qemu-display-listener/src/audio.rs index 5c9be10..5500efa 100644 --- a/qemu-display-listener/src/audio.rs +++ b/qemu-display-listener/src/audio.rs @@ -22,6 +22,29 @@ pub struct PCMInfo { pub be: bool, } +impl PCMInfo { + pub fn gst_caps(&self) -> String { + let format = format!( + "{}{}{}", + if self.is_float { + "F" + } else if self.is_signed { + "S" + } else { + "U" + }, + self.bits, + if self.be { "BE" } else { "LE" } + ); + format!( + "audio/x-raw,format={format},channels={channels},rate={rate},layout=interleaved", + format = format, + channels = self.nchannels, + rate = self.freq, + ) + } +} + #[derive(Debug)] pub struct Volume { pub mute: bool, @@ -63,7 +86,7 @@ trait Audio { #[derivative(Debug)] pub struct Audio { #[derivative(Debug = "ignore")] - pub proxy: AudioProxy<'static>, + pub proxy: AsyncAudioProxy<'static>, } #[derive(Debug)] @@ -235,20 +258,21 @@ impl> AudioInListener { } impl Audio { - pub fn new(conn: &zbus::Connection) -> Result { - let proxy = AudioProxy::new(conn)?; + pub async fn new(conn: &zbus::azync::Connection) -> Result { + let proxy = AsyncAudioProxy::new(conn).await?; Ok(Self { proxy }) } - pub fn available(conn: &zbus::Connection) -> bool { + pub async fn available(conn: &zbus::azync::Connection) -> bool { // TODO: we may want to generalize interface detection - let ip = zbus::fdo::IntrospectableProxy::builder(conn) + let ip = zbus::fdo::AsyncIntrospectableProxy::builder(conn) .destination("org.qemu") .path("/org/qemu/Display1") .unwrap() - .build() + .build_async() + .await .unwrap(); - let introspect = zbus::xml::Node::from_str(&ip.introspect().unwrap()).unwrap(); + let introspect = zbus::xml::Node::from_str(&ip.introspect().await.unwrap()).unwrap(); let has_audio = introspect .nodes() .iter() @@ -256,10 +280,12 @@ impl Audio { has_audio } - pub fn listen_out(&self) -> Result> { + pub async fn listen_out(&self) -> Result> { let (p0, p1) = UnixStream::pair()?; let (tx, rx) = mpsc::channel(); - self.proxy.register_out_listener(p0.as_raw_fd().into())?; + self.proxy + .register_out_listener(p0.as_raw_fd().into()) + .await?; let _thread = thread::spawn(move || { let c = zbus::Connection::new_unix_client(p1, false).unwrap(); @@ -283,10 +309,12 @@ impl Audio { Ok(rx) } - pub fn listen_in(&self) -> Result> { + pub async fn listen_in(&self) -> Result> { let (p0, p1) = UnixStream::pair()?; let (tx, rx) = mpsc::channel(); - self.proxy.register_in_listener(p0.as_raw_fd().into())?; + self.proxy + .register_in_listener(p0.as_raw_fd().into()) + .await?; let _thread = thread::spawn(move || { let c = zbus::Connection::new_unix_client(p1, false).unwrap(); diff --git a/qemu-rdw/src/audio.rs b/qemu-rdw/src/audio.rs new file mode 100644 index 0000000..38f3694 --- /dev/null +++ b/qemu-rdw/src/audio.rs @@ -0,0 +1,66 @@ +use std::error::Error; +use std::result::Result; +use std::thread; + +use qemu_display_listener::Audio; + +#[derive(Debug, Default)] +pub struct Handler { + thread: Option>, +} + +impl Handler { + pub async fn new(conn: &zbus::azync::Connection) -> Result> { + if !Audio::available(conn).await { + log::debug!("No qemu audio provided on the bus"); + return Ok(Self::default()); + } + + let audio = Audio::new(conn).await?; + let rx = audio.listen_out().await?; + let mut gst = rdw::GstAudio::new()?; + + let thread = thread::spawn(move || loop { + match rx.recv() { + Ok(event) => { + use qemu_display_listener::AudioOutEvent::*; + + match event { + Init { id, info } => { + if let Err(e) = gst.init_out(id, &info.gst_caps()) { + log::warn!("Failed to initialize audio stream: {}", e); + } + } + Fini { id } => { + gst.fini_out(id); + } + SetEnabled { id, enabled } => { + if let Err(e) = gst.set_enabled_out(id, enabled) { + log::warn!("Failed to set enabled audio stream: {}", e); + } + } + SetVolume { id, volume } => { + if let Err(e) = gst.set_volume_out( + id, + volume.mute, + volume.volume.first().map(|v| *v as f64 / 255f64), + ) { + log::warn!("Failed to set volume: {}", e); + } + } + Write { id, data } => { + if let Err(e) = gst.write_out(id, data) { + log::warn!("Failed to output stream: {}", e); + } + } + } + } + Err(e) => log::warn!("Audio thread error: {}", e), + } + }); + + Ok(Self { + thread: Some(thread), + }) + } +} diff --git a/qemu-rdw/src/main.rs b/qemu-rdw/src/main.rs index 484aecd..7e6d6fb 100644 --- a/qemu-rdw/src/main.rs +++ b/qemu-rdw/src/main.rs @@ -1,9 +1,11 @@ use gio::ApplicationFlags; use glib::{clone, MainContext}; use gtk::{gio, glib, prelude::*}; +use once_cell::sync::OnceCell; use qemu_display_listener::Console; use zbus::Connection; +mod audio; mod display_qemu; fn main() { @@ -15,6 +17,8 @@ fn main() { .expect("Failed to connect to DBus") .into(); + let audio = std::sync::Arc::new(OnceCell::new()); + app.connect_activate(move |app| { let window = gtk::ApplicationWindow::new(app); @@ -22,11 +26,17 @@ fn main() { window.set_default_size(1024, 768); let conn = conn.clone(); + let audio_clone = audio.clone(); MainContext::default().spawn_local(clone!(@strong window => async move { let console = Console::new(&conn, 0).await.expect("Failed to get the QEMU console"); let display = display_qemu::DisplayQemu::new(console); window.set_child(Some(&display)); + match audio::Handler::new(&conn).await { + Ok(handler) => audio_clone.set(handler).unwrap(), + Err(e) => log::warn!("Failed to setup audio: {}", e), + } + window.show(); })); });