vhost-device-sound: Add GStreamer audio backend support

Add gstreamer backend.

Add gstreamer-related crates used in the vhost-device-sound
module

This affects only the vhost-device-sound module, and updates the
following dependencies:

- dependency-name: gstreamer
  dependency-version: 0.24.2
- dependency-name: gstreamer-app
  dependency-version: 0.24.2
- dependency-name: gstreamer-audio
  dependency-version: 0.24.2

Signed-off-by: nicholasdezai <nicholasdezai@gmail.com>
This commit is contained in:
nicholasdezai 2025-09-26 00:25:34 +08:00 committed by Stefano Garzarella
parent b6fd16f949
commit a0261d689e
10 changed files with 1689 additions and 233 deletions

640
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,7 @@
### Added ### Added
- [[#876]](https://github.com/rust-vmm/vhost-device/pull/876) Add GStreamer audio backend support
- [[#806]](https://github.com/rust-vmm/vhost-device/pull/806) Add controls field in VirtioSoundConfig - [[#806]](https://github.com/rust-vmm/vhost-device/pull/806) Add controls field in VirtioSoundConfig
- [[#746]](https://github.com/rust-vmm/vhost-device/pull/746) Add new sampling rates 12000Hz and 24000Hz - [[#746]](https://github.com/rust-vmm/vhost-device/pull/746) Add new sampling rates 12000Hz and 24000Hz
@ -14,6 +15,10 @@
- [[#808]](https://github.com/rust-vmm/vhost-device/pull/808) pipewire: Fix rand module imports - [[#808]](https://github.com/rust-vmm/vhost-device/pull/808) pipewire: Fix rand module imports
- [[#884]](https://github.com/rust-vmm/vhost-device/pull/884) vhost-device-sound/pipewire: fix wrong format - [[#884]](https://github.com/rust-vmm/vhost-device/pull/884) vhost-device-sound/pipewire: fix wrong format
### Limitations
- GStreamer backend: 20-bit PCM formats (VIRTIO_SND_PCM_FMT_S20/U20) are not directly supported by GStreamer and are automatically converted to 24/32-bit formats
### Deprecated ### Deprecated
## v0.2.0 ## v0.2.0

View file

@ -12,9 +12,10 @@ edition = "2021"
[features] [features]
xen = ["vm-memory/xen", "vhost/xen", "vhost-user-backend/xen"] xen = ["vm-memory/xen", "vhost/xen", "vhost-user-backend/xen"]
default = ["alsa-backend", "pw-backend"] default = ["alsa-backend", "pw-backend", "gst-backend"]
alsa-backend = ["dep:alsa"] alsa-backend = ["dep:alsa"]
pw-backend = ["pw"] pw-backend = ["pw"]
gst-backend = ["dep:gst", "dep:gst-app", "dep:gst-audio"]
[dependencies] [dependencies]
clap = { version = "4.5", features = ["derive"] } clap = { version = "4.5", features = ["derive"] }
@ -32,6 +33,9 @@ vmm-sys-util = "0.14"
[target.'cfg(target_env = "gnu")'.dependencies] [target.'cfg(target_env = "gnu")'.dependencies]
alsa = { version = "0.10", optional = true } alsa = { version = "0.10", optional = true }
pw = { package = "pipewire", version = "0.9.2", optional = true } pw = { package = "pipewire", version = "0.9.2", optional = true }
gst = { package = "gstreamer", version = "0.24.2", optional = true, features = ["v1_24"] }
gst-app = { package = "gstreamer-app", version = "0.24.2", optional = true, features = ["v1_24"] }
gst-audio = {package = "gstreamer-audio", version = "0.24.2", optional = true, features = ["v1_24"] }
[dev-dependencies] [dev-dependencies]
rstest = "0.26.1" rstest = "0.26.1"

View file

@ -16,7 +16,7 @@ generated with help2man target/debug/vhost-device-sound |mandoc
vhost-user Unix domain socket path vhost-user Unix domain socket path
--backend <BACKEND> --backend <BACKEND>
audio backend to be used [possible values: null, pipewire, alsa] audio backend to be used [possible values: null, pipewire, alsa, gstreamer]
-h, --help -h, --help
Print help Print help

View file

@ -25,4 +25,7 @@ pub enum BackendType {
Pipewire, Pipewire,
#[cfg(all(feature = "alsa-backend", target_env = "gnu"))] #[cfg(all(feature = "alsa-backend", target_env = "gnu"))]
Alsa, Alsa,
#[cfg(all(feature = "gst-backend", target_env = "gnu"))]
#[value(name = "gstreamer")]
GStreamer,
} }

View file

@ -8,10 +8,15 @@ mod null;
#[cfg(all(feature = "pw-backend", target_env = "gnu"))] #[cfg(all(feature = "pw-backend", target_env = "gnu"))]
mod pipewire; mod pipewire;
#[cfg(all(feature = "gst-backend", target_env = "gnu"))]
mod gstreamer;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
#[cfg(all(feature = "alsa-backend", target_env = "gnu"))] #[cfg(all(feature = "alsa-backend", target_env = "gnu"))]
use self::alsa::AlsaBackend; use self::alsa::AlsaBackend;
#[cfg(all(feature = "gst-backend", target_env = "gnu"))]
use self::gstreamer::GStreamerBackend;
use self::null::NullBackend; use self::null::NullBackend;
#[cfg(all(feature = "pw-backend", target_env = "gnu"))] #[cfg(all(feature = "pw-backend", target_env = "gnu"))]
use self::pipewire::PwBackend; use self::pipewire::PwBackend;
@ -61,6 +66,12 @@ pub fn alloc_audio_backend(
} }
#[cfg(all(feature = "alsa-backend", target_env = "gnu"))] #[cfg(all(feature = "alsa-backend", target_env = "gnu"))]
BackendType::Alsa => Ok(Box::new(AlsaBackend::new(streams))), BackendType::Alsa => Ok(Box::new(AlsaBackend::new(streams))),
#[cfg(all(feature = "gst-backend", target_env = "gnu"))]
BackendType::GStreamer => {
Ok(Box::new(GStreamerBackend::new(streams).map_err(|err| {
crate::Error::UnexpectedAudioBackendError(err.into())
})?))
}
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,68 @@
use std::env;
use tempfile::TempDir;
/// A test harness that sets up an isolated environment for GStreamer tests.
/// Unlike PipeWire, GStreamer is a library and doesn't need a daemon.
/// We only need to isolate runtime dirs and registry files.
pub struct GStreamerTestHarness {
_runtime_dir: TempDir,
old_runtime_dir: Option<String>,
old_gst_registry: Option<String>,
old_gst_debug: Option<String>,
}
impl GStreamerTestHarness {
/// Create a new isolated GStreamer test environment.
pub fn new() -> Self {
let tmpdir = tempfile::tempdir().expect("Failed to create temp dir for GStreamer");
let old_runtime_dir = env::var("XDG_RUNTIME_DIR").ok();
let old_gst_registry = env::var("GST_REGISTRY").ok();
let old_gst_debug = env::var("GST_DEBUG").ok();
log::debug!("old_runtime_dir: {:?}", old_runtime_dir);
log::debug!("old_gst_registry: {:?}", old_gst_registry);
log::debug!("old_gst_debug: {:?}", old_gst_debug);
env::set_var("XDG_RUNTIME_DIR", tmpdir.path());
env::set_var("GST_REGISTRY", tmpdir.path().join("gst-registry.bin"));
env::set_var("GST_DEBUG", "ERROR");
log::debug!(
"Started isolated GStreamer test environment at {:?}",
tmpdir.path()
);
Self {
_runtime_dir: tmpdir,
old_runtime_dir,
old_gst_registry,
old_gst_debug,
}
}
}
impl Drop for GStreamerTestHarness {
fn drop(&mut self) {
if let Some(val) = &self.old_runtime_dir {
env::set_var("XDG_RUNTIME_DIR", val);
} else {
env::remove_var("XDG_RUNTIME_DIR");
}
if let Some(val) = &self.old_gst_registry {
env::set_var("GST_REGISTRY", val);
} else {
env::remove_var("GST_REGISTRY");
}
if let Some(val) = &self.old_gst_debug {
env::set_var("GST_DEBUG", val);
} else {
env::remove_var("GST_DEBUG");
}
log::debug!("Isolated GStreamer environment cleaned up");
}
}

View file

@ -55,6 +55,10 @@ mod tests {
all(feature = "alsa-backend", target_env = "gnu"), all(feature = "alsa-backend", target_env = "gnu"),
case::alsa("alsa", BackendType::Alsa) case::alsa("alsa", BackendType::Alsa)
)] )]
#[cfg_attr(
all(feature = "gst-backend", target_env = "gnu"),
case::gstreamer("gstreamer", BackendType::GStreamer)
)]
fn test_cli_backend_arg(#[case] backend_name: &str, #[case] backend: BackendType) { fn test_cli_backend_arg(#[case] backend_name: &str, #[case] backend: BackendType) {
let args: SoundArgs = Parser::parse_from([ let args: SoundArgs = Parser::parse_from([
"", "",

View file

@ -29,6 +29,12 @@ pub enum Error {
DescriptorWriteFailed, DescriptorWriteFailed,
#[error("Could not disconnect stream")] #[error("Could not disconnect stream")]
CouldNotDisconnectStream, CouldNotDisconnectStream,
#[cfg(feature = "gst-backend")]
#[error("Could not start stream")]
CouldNotStartStream,
#[cfg(feature = "gst-backend")]
#[error("Could not stop stream")]
CouldNotStopStream,
} }
type Result<T> = std::result::Result<T, Error>; type Result<T> = std::result::Result<T, Error>;
@ -323,7 +329,8 @@ impl Request {
} }
#[inline] #[inline]
/// Returns the length of the sound data [`virtio_queue::desc::RawDescriptor`]. /// Returns the length of the sound data
/// [`virtio_queue::desc::RawDescriptor`].
pub const fn len(&self) -> usize { pub const fn len(&self) -> usize {
self.len self.len
} }