208 lines
8.1 KiB
Markdown
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
|
|
```
|