8.1 KiB
vmic
Create and manage PipeWire virtual microphones easily.
Vmic wraps pw-loopback/pactl to turn ad-hoc loopback chains into named
profiles: create a virtual sink/mic pair once, then route apps and hardware
sources into it by name. Each vmic runs the cheapest PipeWire node shape that
satisfies its current settings, and vmic migrates it live as those settings
change.
Features
- Dynamic topology: a vmic starts as a single
pw-loopbackprocess (2 nodes, sink straight into mic) and only upgrades to an isolated 4-node shape when it's actually needed - a self-monitor loopback and a mixed-in hardware source active at once. It downgrades back the moment either goes away. --loopback-no-mixopt-out: keep the cheaper 2-node shape even with both active, accepting that the mixed-in source becomes audible in the self-monitor loopback in exchange for never running the second stage.- Automatic stream reconnection: app streams already routed into or out of a vmic are moved to the new nodes when a topology migration happens - nothing needs to be re-selected in the app.
- Spawn-before-teardown migrations: a topology change always spawns the new node pair(s) and confirms they're alive before touching the old ones, so a failed migration never leaves a vmic worse off than before the command ran.
- Self-monitor loopback with its own adjustable volume, independent of the mixed-in hardware source's volume.
- Status and listing showing liveness and the vmic's current node architecture, so the tradeoff of any given shape is always visible.
wipe, a full teardown of every vmic-related PipeWire node, module, and process on the system - tracked or not.- Shell completions for bash, zsh, fish, and others.
Installation
Requires a Rust toolchain, and PipeWire + pipewire-pulse (pactl,
pw-loopback) on PATH.
cargo build --release
The binary is written to target/release/vmic. Place it on PATH, e.g.:
install -Dm755 target/release/vmic ~/.local/bin/vmic
If the build fails inside libspa-sys (e.g. no field 'data' on type 'spa_pod_builder'), your system clang is newer than the pinned bindgen
version supports. Point LIBCLANG_PATH at an older libclang - a quick fix
is a pip-vendored version:
python3 -m venv ~/.local/share/vmic-build/libclang-venv
~/.local/share/vmic-build/libclang-venv/bin/pip install libclang
then add to .cargo/config.toml:
[env]
LIBCLANG_PATH = "/home/<you>/.local/share/vmic-build/libclang-venv/lib/python3.*/site-packages/clang/native"
Commands
| Command | Aliases | Description |
|---|---|---|
create |
mk, make |
Create a new virtual microphone. |
route |
Move app streams and mix a hardware source in. | |
edit |
Change loopback and volume settings on a vmic. | |
delete |
rm, remove |
Delete a virtual microphone. |
list |
ls |
List all virtual microphones with live status. |
wipe |
reset |
Tear down every vmic, tracked or not. |
completions |
Generate a shell completion script. |
Run vmic or vmic --help for the full flag reference.
create flags
| Flag | Value | Meaning | Default |
|---|---|---|---|
-l, --loopback |
(bare) | Enable a self-monitor loopback at creation. | off |
route flags
| Flag | Value | Meaning | Default |
|---|---|---|---|
-i, --input |
APP[:MEDIA] |
Move matching sink-inputs into this vmic's sink. | - |
-o, --output |
APP[:MEDIA] |
Move matching source-outputs off this vmic's source. | - |
-s, --source |
SOURCE|none |
Mix a hardware source into the vmic, or remove it. | - |
edit flags
| Flag | Value | Meaning | Default |
|---|---|---|---|
-l, --loopback |
[BOOL] |
Enable/disable the self-monitor loopback (bare -l means true). |
- |
--loopback-no-mix |
[BOOL] |
Keep the 2-node shape even with loopback + a mixed source (bare means true). |
false |
-v, --volume |
PCT |
Self-monitor loopback volume: fraction (0.8) or percent (80). |
- |
-sv, --source-volume |
PCT |
Mixed-in source volume: fraction (0.8) or percent (80). |
- |
edit only touches the fields given on the command line; everything else
stays as-is. Any flag that changes --loopback/--loopback-no-mix may
trigger a topology migration - see below.
Examples
# Create a vmic with a self-monitor loopback.
vmic create podcast -l
# Point your app's output device at vmic_podcast_sink, and your
# recorder's input device at vmic_podcast_mic, then mix in a real mic.
vmic route podcast -s "USB Microphone"
# Move Firefox's playback into the vmic, and OBS's recording off it.
vmic route podcast -i firefox
vmic route podcast -o obs
# Stop isolating the mixed source from the self-monitor, collapsing back
# to the cheaper 2-node shape.
vmic edit podcast --loopback-no-mix
# Adjust volumes independently.
vmic edit podcast -v 0.8
vmic edit podcast -sv 60
# Remove the mixed source.
vmic route podcast -s none
# Inspect status, including current node architecture.
vmic list
# Tear everything down.
vmic wipe
How the topology adapts
A vmic always exposes the same two ports - vmic_<name>_sink for apps to
play into, vmic_<name>_mic for recorders to capture from - but how many
PipeWire nodes back them depends on what's active:
Simple2Node (default):
apps -> vmic_X_sink =(pw-loopback)=> vmic_X_mic -> recorders
Pure4Node (loopback + mixed source, isolated):
apps -> vmic_X_sink =(stage 1)=> vmic_X_mid -> vmic_X_mix =(stage 2)=> vmic_X_mic -> recorders
^ hardware source joins here instead
Pure4Node only exists to keep a mixed-in hardware source out of the
self-monitor loopback's ears - it's the shape vmic switches to the moment
both edit -l and route -s are active at once, and switches back out of
the moment either stops being true. edit --loopback-no-mix opts a vmic out
of ever making that switch: it stays at Simple2Node, and the mixed source
just becomes audible in the self-monitor too.
Switching shape necessarily recreates the nodes behind sink/mic - same
names, new underlying ids - because a pw-loopback process's capture and
playback nodes share one lifecycle and can't be reconfigured live. Each
migration:
- Spawns the new node pair(s) first. This is the only step that can fail; if it does, the old topology is completely untouched.
- Only then tears down the old node pair(s), best-effort.
- Reconnects any app streams that were routed into or out of the old nodes, by id, so nothing needs to be re-selected.
- Re-applies the self-monitor loopback and any mixed-in source to the new nodes.
- Reports what happened - always, whether triggered by
route -soredit -l/edit --loopback-no-mix.
vmic list shows each vmic's current shape under architecture:.
State on disk
Tracked in a SQLite database at $XDG_CONFIG_HOME/vmic/vmic.db, one row per
vmic (names, node ids, pids, loopback/volume/mixed-source settings). The
directory can be overridden with the VMIC_STATE_DIR_OVERRIDE environment
variable.
Shell completions
vmic completions bash > /etc/bash_completion.d/vmic
vmic completions zsh > "${fpath[1]}/_vmic"
vmic completions fish > ~/.config/fish/completions/vmic.fish