Files
vmic/README.md

4.6 KiB

vmic

Create and manage PipeWire virtual microphones easily, from the command line.


What it does

A vmic starts as a single loopback stage:

apps -> vmic_X_sink =(pw-loopback)=> vmic_X_mic -> recorders

Point an app's output device at vmic_X_sink, and a recording app's input device at vmic_X_mic. Optionally mix in a real microphone (route -s), and optionally hear your own output via a self-monitor loopback (edit -l).

If you enable both a self-monitor loopback and a mixed-in hardware source at the same time, vmic automatically splits into a second loopback stage so the hardware source never leaks into your self-monitor:

apps -> vmic_X_sink =(stage 1)=> vmic_X_mid -> vmic_X_mix =(stage 2)=> vmic_X_mic -> recorders
                                                    ^ hardware mic joins here instead

It collapses back to the single-stage form the moment either the loopback or the mixed-in source goes away. edit --loopback-no-mix true opts a vmic out of this split entirely, keeping it at one stage even with both active - the mixed source just becomes audible in your self-monitor too, as an explicit tradeoff for staying simpler. Either transition recreates the vmic's underlying sink/mic nodes (same names, new ids); any stream that was routed into or out of them gets moved back automatically, and vmic reports what happened either way.

Requirements

  • PipeWire + pipewire-pulse (pactl, pw-loopback on PATH)
  • Rust (edition 2021)

Build

cargo build --release

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 one:

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"

Usage

vmic create <name> [-l]                             # create a vmic, optionally with self-monitor
vmic route  <name> -i <app[:media]>                  # move an app's playback into the vmic
vmic route  <name> -o <app[:media]>                  # move an app's recording off the vmic
vmic route  <name> -s <source|none>                  # mix a hardware mic in, or remove it
vmic edit   <name> -l [<true|false>]                 # toggle the self-monitor loopback
vmic edit   <name> --loopback-no-mix [<true|false>]  # keep 1 stage even with loopback + a mixed source
vmic edit   <name> -v <0.8|80>                       # self-monitor loopback volume
vmic edit   <name> -sv <0.8|80>                      # mixed-in source volume
vmic delete <name>                                   # tear down one vmic
vmic list                                            # show all vmics, their status, and current node shape
vmic wipe                                            # tear down every vmic, tracked or not

-l and --loopback-no-mix accept a bare form (means true) or an explicit true/false, e.g. vmic edit podcast -l and vmic edit podcast -l false both work.

Aliases: create = mk/make, delete = rm/remove, list = ls, wipe = reset.

Shell completions: vmic completions <bash|zsh|fish|...>.

Example

vmic create podcast -l
# set your app's output device to vmic_podcast_sink
# set your recorder's input device to vmic_podcast_mic
vmic route podcast -s "USB Microphone"

State

Tracked in a SQLite database at ~/.config/vmic/vmic.db (override the directory with VMIC_STATE_DIR_OVERRIDE).

Testing

cargo test runs a handful of fast unit tests only - no PipeWire needed.

Two live integration test suites exercise every command against a real PipeWire session: they spawn real pw-loopback processes, move real streams, and (guarded) run the destructive wipe command. Neither runs automatically; opt in explicitly:

tests/live_test.sh --input-app firefox --output-app chromium --hw-source fifine
cargo test --test live_test -- --ignored --nocapture

Both take the same parameters - as flags for the shell version, or matching env vars for either (VMIC_TEST_INPUT_APP, VMIC_TEST_OUTPUT_APP, VMIC_TEST_HW_SOURCE, VMIC_TEST_NAME, VMIC_TEST_SKIP_WIPE=1) - so point them at whatever apps and hardware source you actually have available. They only ever touch vmics named <name>/<name>_a/<name>_b, refuse to run the wipe phase if any other vmic exists at that point, and always clean up after themselves even on failure.

License

APGL-3