Playback starts working

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
This commit is contained in:
Marc-André Lureau 2021-03-07 14:55:19 +04:00
parent 262dd60a03
commit 3b7f0c5793
6 changed files with 169 additions and 63 deletions

View File

@ -8,55 +8,32 @@ use zbus::{dbus_interface, dbus_proxy, export::zvariant::Fd};
use crate::{EventSender, Result}; use crate::{EventSender, Result};
#[derive(Debug)]
pub struct PCMInfo {
pub bits: u8,
pub is_signed: bool,
pub is_float: bool,
pub freq: u32,
pub nchannels: u8,
pub bytes_per_frame: u32,
pub bytes_per_second: u32,
pub be: bool,
}
#[derive(Debug)] #[derive(Debug)]
pub enum AudioOutEvent { pub enum AudioOutEvent {
Init { Init { id: u64, info: PCMInfo },
id: u64, Fini { id: u64 },
bits: u8, SetEnabled { id: u64, enabled: bool },
is_signed: bool, Write { id: u64, data: Vec<u8> },
is_float: bool,
freq: u32,
nchannels: u8,
bytes_per_frame: u32,
bytes_per_second: u32,
swap_endianness: u32,
},
Fini {
id: u64,
},
SetEnabled {
id: u64,
enabled: bool,
},
Write {
id: u64,
data: Vec<u8>,
},
} }
#[derive(Debug)] #[derive(Debug)]
pub enum AudioInEvent { pub enum AudioInEvent {
Init { Init { id: u64, info: PCMInfo },
id: u64, Fini { id: u64 },
bits: u8, SetEnabled { id: u64, enabled: bool },
is_signed: bool, Read { id: u64 },
is_float: bool,
freq: u32,
nchannels: u8,
bytes_per_frame: u32,
bytes_per_second: u32,
swap_endianness: u32,
},
Fini {
id: u64,
},
SetEnabled {
id: u64,
enabled: bool,
},
Read {
id: u64,
},
} }
#[dbus_proxy( #[dbus_proxy(
@ -115,18 +92,20 @@ impl<E: 'static + EventSender<Event = AudioOutEvent>> AudioOutListener<E> {
nchannels: u8, nchannels: u8,
bytes_per_frame: u32, bytes_per_frame: u32,
bytes_per_second: u32, bytes_per_second: u32,
swap_endianness: u32, be: bool,
) { ) {
self.send(AudioOutEvent::Init { self.send(AudioOutEvent::Init {
id, id,
bits, info: PCMInfo {
is_signed, bits,
is_float, is_signed,
freq, is_float,
nchannels, freq,
bytes_per_frame, nchannels,
bytes_per_second, bytes_per_frame,
swap_endianness, bytes_per_second,
be,
},
}) })
} }
@ -185,18 +164,20 @@ impl<E: 'static + EventSender<Event = AudioInEvent>> AudioInListener<E> {
nchannels: u8, nchannels: u8,
bytes_per_frame: u32, bytes_per_frame: u32,
bytes_per_second: u32, bytes_per_second: u32,
swap_endianness: u32, be: bool,
) { ) {
self.send(AudioInEvent::Init { self.send(AudioInEvent::Init {
id, id,
bits, info: PCMInfo {
is_signed, bits,
is_float, is_signed,
freq, is_float,
nchannels, freq,
bytes_per_frame, nchannels,
bytes_per_second, bytes_per_frame,
swap_endianness, bytes_per_second,
be,
},
}) })
} }

View File

@ -1,4 +1,4 @@
use std::sync::mpsc::{Sender, SendError}; use std::sync::mpsc::{SendError, Sender};
pub(crate) trait EventSender { pub(crate) trait EventSender {
type Event; type Event;

View File

@ -18,6 +18,8 @@ libloading = "0.6"
gl = "0.14.0" gl = "0.14.0"
glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true } glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true }
derivative = "2.2.0" derivative = "2.2.0"
gst = { package = "gstreamer", version = "0.16.7" }
gst-app = { package = "gstreamer-app", version = "0.16.5" }
[dependencies.gtk] [dependencies.gtk]
package = "gtk4" package = "gtk4"

View File

@ -11,7 +11,8 @@ use log::{debug, info};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::env; use std::env;
use qemu_display_listener::Console; use crate::gstaudio::GstAudio;
use qemu_display_listener::{Audio, Console};
use zbus::Connection; use zbus::Connection;
mod imp { mod imp {
@ -23,6 +24,7 @@ mod imp {
pub window: OnceCell<WeakRef<QemuApplicationWindow>>, pub window: OnceCell<WeakRef<QemuApplicationWindow>>,
pub conn: OnceCell<Connection>, pub conn: OnceCell<Connection>,
pub addr: OnceCell<String>, pub addr: OnceCell<String>,
pub audio: OnceCell<GstAudio>,
} }
impl ObjectSubclass for QemuApplication { impl ObjectSubclass for QemuApplication {
@ -40,6 +42,7 @@ mod imp {
window: OnceCell::new(), window: OnceCell::new(),
conn: OnceCell::new(), conn: OnceCell::new(),
addr: OnceCell::new(), addr: OnceCell::new(),
audio: OnceCell::new(),
} }
} }
} }
@ -92,6 +95,12 @@ mod imp {
Connection::new_session() Connection::new_session()
} }
.expect("Failed to connect to DBus"); .expect("Failed to connect to DBus");
if let Ok(audio) = Audio::new(&conn) {
self.audio
.set(GstAudio::new(audio).expect("Failed to setup audio"))
.expect("Audio already set");
}
let console = Console::new(&conn, 0).expect("Failed to get the console"); let console = Console::new(&conn, 0).expect("Failed to get the console");
self.conn.set(conn).expect("Connection already set."); self.conn.set(conn).expect("Connection already set.");

113
qemu-gtk4/src/gstaudio.rs Normal file
View File

@ -0,0 +1,113 @@
use gst::prelude::*;
use qemu_display_listener::{Audio, PCMInfo};
use std::thread::{self, JoinHandle};
use std::{collections::HashMap, error::Error};
#[derive(Debug)]
struct OutStream {
pipeline: gst::Pipeline,
src: gst_app::AppSrc,
}
fn pcminfo_as_caps(info: &PCMInfo) -> String {
let format = format!(
"{}{}{}",
if info.is_float {
"F"
} else {
if info.is_signed {
"S"
} else {
"U"
}
},
info.bits,
if info.be { "BE" } else { "LE" }
);
format!(
"audio/x-raw,format={format},channels={channels},rate={rate},layout=interleaved",
format = format,
channels = info.nchannels,
rate = info.freq,
)
}
impl OutStream {
fn new(info: &PCMInfo) -> Result<Self, Box<dyn Error>> {
let caps = pcminfo_as_caps(info);
let pipeline = &format!("appsrc name=src is-live=1 do-timestamp=0 format=time caps=\"{}\" ! queue ! audioconvert ! audioresample ! autoaudiosink name=sink", caps);
let pipeline = gst::parse_launch(pipeline)?;
let pipeline = pipeline.dynamic_cast::<gst::Pipeline>().unwrap();
let src = pipeline
.get_by_name("src")
.unwrap()
.dynamic_cast::<gst_app::AppSrc>()
.unwrap();
Ok(Self { pipeline, src })
}
}
#[derive(Debug)]
pub struct GstAudio {
thread: JoinHandle<()>,
}
impl GstAudio {
pub fn new(audio: Audio) -> Result<Self, Box<dyn Error>> {
gst::init()?;
let rx = audio.listen_out()?;
let mut out = HashMap::new();
let thread = thread::spawn(move || loop {
match rx.recv() {
Ok(event) => {
use qemu_display_listener::AudioOutEvent::*;
match event {
Init { id, info } => {
if out.contains_key(&id) {
eprintln!("Invalid Init, id {} is already setup", id);
continue;
}
match OutStream::new(&info) {
Ok(s) => {
out.insert(id, s);
}
Err(e) => {
eprintln!("Failed to create stream: {}", e);
}
}
}
Fini { id } => {
out.remove(&id);
}
SetEnabled { id, enabled } => {
if let Some(s) = out.get(&id) {
if let Err(e) = s.pipeline.set_state(if enabled {
gst::State::Playing
} else {
gst::State::Ready
}) {
eprintln!("Failed to change state: {}", e);
}
} else {
eprintln!("Stream was not setup yet: {}", id);
}
}
Write { id, data } => {
if let Some(s) = out.get(&id) {
let b = gst::Buffer::from_slice(data);
let _ = s.src.push_buffer(b);
} else {
eprintln!("Stream was not setup yet: {}", id);
}
}
}
}
Err(e) => eprintln!("Audio thread error: {}", e),
}
});
Ok(Self { thread })
}
}

View File

@ -6,6 +6,7 @@ mod console;
mod console_area; mod console_area;
mod egl; mod egl;
mod error; mod error;
mod gstaudio;
mod window; mod window;
use application::QemuApplication; use application::QemuApplication;