mirror of
https://github.com/rustdesk/qemu-display.git
synced 2025-08-17 16:25:39 +00:00
Playback starts working
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
This commit is contained in:
parent
262dd60a03
commit
3b7f0c5793
@ -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,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
|
@ -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
113
qemu-gtk4/src/gstaudio.rs
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user