Files
vmic/README.md

208 lines
8.1 KiB
Markdown

# 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-loopback` process (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-mix` opt-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`.
```sh
cargo build --release
```
The binary is written to `target/release/vmic`. Place it on `PATH`, e.g.:
```sh
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:
```sh
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`:
```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
```sh
# 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:
1. Spawns the new node pair(s) first. This is the only step that can fail;
if it does, the old topology is completely untouched.
2. Only then tears down the old node pair(s), best-effort.
3. Reconnects any app streams that were routed into or out of the old nodes,
by id, so nothing needs to be re-selected.
4. Re-applies the self-monitor loopback and any mixed-in source to the new
nodes.
5. Reports what happened - always, whether triggered by `route -s` or
`edit -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
```sh
vmic completions bash > /etc/bash_completion.d/vmic
vmic completions zsh > "${fpath[1]}/_vmic"
vmic completions fish > ~/.config/fish/completions/vmic.fish
```