Expand description
§Rust bindings for pipewire
pipewire is a crate offering Rust bindings to libpipewire, the library for interacting
with the pipewire server.
Programs that interact with pipewire usually react to events from the server by registering callbacks and invoke methods on objects on the server by calling methods on local proxy objects.
§Getting started
Most programs that interact with pipewire will need the same few basic objects:
- A
MainLoopthat drives the program, reacting to any incoming events and dispatching method calls. Most of a time, the program/thread will sit idle in this loop, waiting on events to occur. - A
Contextthat keeps track of any pipewire resources. - A
Corethat is a proxy for the remote pipewire instance, used to send messages to and receive events from the remote server. - Optionally, a
Registrythat can be used to manage and track available objects on the server.
This is how they can be created:
use pipewire::{main_loop::MainLoopBox, context::ContextBox};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mainloop = MainLoopBox::new(None)?;
let context = ContextBox::new(&mainloop.loop_(), None)?;
let core = context.connect(None)?;
let registry = core.get_registry()?;
Ok(())
}§Smart pointers to PipeWire objects
The example above uses std::boxed::Box-like smart pointers to create the needed objects.
Those boxes use lifetimes to ensure the objects dependencies (e.g. Core depends on MainLoop)
outlive the object itself.
If more flexibility is needed, std::rc::Rc-like reference-counting smart pointers also exist.
Those will automatically keep the objects dependencies alive until the object is destroyed.
Both of these kinds of types will automatically dereference to non-owning references for shared
functionality, e.g. &MainLoop or &Core.
The same example as above, but using Rc types:
use pipewire::{main_loop::MainLoopRc, context::ContextRc};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mainloop = MainLoopRc::new(None)?;
let context = ContextRc::new(&mainloop, None)?;
let core = context.connect_rc(None)?;
let registry = core.get_registry_rc()?;
Ok(())
}§Listening for events
Once the needed objects are created, you can start hooking up different kinds of callbacks to them to react to events, and call methods to change the state of the remote.
use pipewire::{main_loop::MainLoopBox, context::ContextBox};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mainloop = MainLoopBox::new(None)?;
let context = ContextBox::new(&mainloop.loop_(), None)?;
let core = context.connect(None)?;
let registry = core.get_registry()?;
// Register a callback to the `global` event on the registry, which notifies of any new global objects
// appearing on the remote.
// The callback will only get called as long as we keep the returned listener alive.
let _listener = registry
.add_listener_local()
.global(|global| println!("New global: {:?}", global))
.register();
// Calling the `destroy_global` method on the registry will destroy the object with the specified id on the remote.
// We don't have a specific object to destroy now, so this is commented out.
// registry.destroy_global(313).into_result()?;
mainloop.run();
Ok(())
}Note that registering any callback requires the closure to have the 'static lifetime, so if you need to capture
any variables, use move || closures, and use std::rc::Rcs to access shared variables
and some std::cell variant if you need to mutate them.
Also note that we called mainloop.run() at the end.
This will enter the loop, and won’t return until we call mainloop.quit() from some event.
If we didn’t run the loop, events and method invocations would not be processed, so the program would terminate
without doing much.
§The main loop
Sometimes, other stuff needs to be done even though we are waiting inside the main loop.
This can be done by adding sources to the loop.
For example, we can call a function on an interval:
use pipewire::main_loop::MainLoopBox;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mainloop = MainLoopBox::new(None)?;
let timer = mainloop.loop_().add_timer(|_| println!("Hello"));
// Call the first time in half a second, and then in a one second interval.
timer.update_timer(Some(Duration::from_millis(500)), Some(Duration::from_secs(1))).into_result()?;
mainloop.run();
Ok(())
}This program will print out “Hello” every second forever.
Using similar methods, you can also react to IO or Signals, or call a callback whenever the loop is idle.
§Multithreading
The pipewire library is not really thread-safe, so pipewire objects do not implement Send
or Sync.
However, you can spawn a MainLoop in another thread and do bidirectional communication using two channels.
To send messages to the main thread, we can easily use a std::sync::mpsc.
Because we are stuck in the main loop in the pipewire thread and can’t just block on receiving a message,
we use a pipewire::channel instead.
See the pipewire::channel module for details.
§Useful links
For info on more general concepts about PipeWire as well as the C library libpipewire, see PipeWire’s
documentation. Some notable pages are:
Re-exports§
Modules§
- buffer
- channel
- A channel for communicating with a thread running a PipeWire loop.
- client
- Clients represent an open connection of a client process with the server.
- constants
- Pipewire constants.
- context
- The context manages all locally available resources.
- core
- The core singleton object used in communication with a PipeWire instance.
- device
- Devices model physical hardware or software devices in the system and can create other objects such as nodes or other devices.
- factory
- Factories are objects that create other objects.
- keys
- A collection of keys that are used to add extra information on objects.
- link
- Links connect two ports of opposite direction, making media flow from the output port to the input port.
- loop_
- The PipeWire event loop responsible for listening to and handling events.
- main_
loop - Wrapper that runs a loop in the current thread.
- metadata
- module
- Modules implement functionality such as providing new objects or policies.
- node
- Nodes are media processing elements that consume or produce buffers with data such as audio or video.
- permissions
- Permissions are used to implement access control for PipeWire clients.
- port
- Ports are attached on nodes and provide interfaces for input or output of data on the node.
- properties
- Properties are used to pass around arbitrary key/value pairs.
- proxy
- Proxy are client side representations of resources that live on a remote PipeWire instance.
- registry
- The registry is a singleton object that keeps track of global objects on the PipeWire instance.
- stream
- Streams are higher-level objects providing a convenient way to send and receive data streams to/from PipeWire.
- thread_
loop - types
Macros§
- __
properties__ - A macro for creating a new
Propertiesstruct with predefined key-value pairs.