micpipe is a lightweight macOS audio router for sending your microphone into
BlackHole 2ch or another CoreAudio output. This lets a call, meeting, or
recording app use BlackHole as its microphone input while receiving both your
voice and other audio routed to the virtual device.
Run it in the foreground for testing, or install it as a per-user launchd
service.
- Routes one input device to one output device.
- Runs as a per-user
launchdservice. - Follows the system default input, or pins a specific input by name.
- Restarts automatically when the system default input changes.
- Restarts automatically when the default input disconnects.
- Waits for a pinned input to reconnect, then restarts automatically.
- On macOS, starts audio streams only while another app is actively using the selected output as an input.
- Stops the audio streams when no app is actively reading the selected output.
micpipe is not a full multi-source mixer. It does not itself combine multiple
inputs, expose gain controls, or provide mute/solo controls. Instead, it routes
one microphone input to one output; other apps can send additional audio to
BlackHole separately.
- macOS
- Rust 1.88 or newer, with Cargo
- BlackHole, or another CoreAudio output device to receive the microphone audio
- Microphone permission for the terminal or installed binary that runs
micpipe
Install from crates.io:
cargo install micpipeInstall from a source checkout:
cargo install --path .Run in the foreground with the default route:
micpipe runBy default, micpipe run follows the system default input device and routes it
to the first output device whose description contains BlackHole 2ch.
On macOS, micpipe waits for an app to actively select that output as an input
before it starts the audio streams. For example, select BlackHole 2ch as the
microphone in a calling app, then join or start the call. Configure the other
audio you want to share to use BlackHole 2ch as its output as well.
Install and start the background service:
micpipe install --output "BlackHole 2ch"Pin explicit devices when you want stable matching by device-name substring:
micpipe install \
--input "MacBook Pro Microphone" \
--output "BlackHole 2ch"Use run instead of install for foreground testing:
micpipe run \
--input "MacBook Pro Microphone" \
--output "BlackHole 2ch"Add --debug to log buffer occupancy once per second:
micpipe run --debugThis test uses built-in macOS tools to record your microphone and a generated
voice through BlackHole while playing the same generated voice through your
current system output. It does not require a Multi-Output Device or pinning an
input in micpipe.
-
Open QuickTime Player, choose File > New Audio Recording, and select
BlackHole 2chas the recording's microphone. Keep the monitoring volume at zero during the test to prevent feedback. -
In one Terminal window, start
micpipeand leave it running:micpipe run
With no
--input,micpipefollows your current default microphone. -
Start recording in QuickTime, then wait until the first Terminal reports
Mic -> BlackHole 2ch running while output is being used as input. -
Speak into your microphone and run this in a second Terminal window:
say -a "BlackHole 2ch" "This audio is playing through BlackHole." & say "This audio is playing through BlackHole." wait
The first say process sends the phrase to BlackHole while the second sends it
to your current system output, so you should hear it immediately. Stop and play
the QuickTime recording; it should contain both your microphone from micpipe
and the generated voice sent to BlackHole. Press Control-C in the first Terminal
window when you are finished.
For a call, select BlackHole 2ch as the call app's microphone and send the
audio you want to share to BlackHole. Keep the call app's speaker output on your
headphones or speakers so its incoming audio is not fed back into BlackHole.
micpipe was built for Tuple's
Dad Joke Greeter, which
speaks a joke when someone joins a room. The greeter sends generated audio to
BlackHole while micpipe sends the user's microphone to the same virtual
device. Tuple selects BlackHole as its microphone and receives both.
The same routing can be used with another call, meeting, or recording app that accepts BlackHole as a microphone input.
Install BlackHole separately before using the default output route.
Run micpipe run from the terminal, then select its output as an input in an
app if macOS needs to prompt for microphone permission. The permission belongs
to the app or binary that starts micpipe, so the foreground test is the
easiest way to confirm capture works before installing the service.
micpipe run [--input <name>] [--output <name>] [--debug]
micpipe install [--input <name>] [--output <name>] [--debug]
micpipe status
micpipe restart
micpipe stop
micpipe start
micpipe uninstall
--output is a case-insensitive substring match and defaults to
BlackHole 2ch.
--input is optional:
- Without
--input,micpipefollows the system default input device. If the system default input changes, or if the current default input disconnects,micpipelogs the change and immediately asks the installed service to restart. - With
--input,micpipepins that input by case-insensitive substring. If the pinned input disconnects,micpipelogs the disconnected device, stops the active audio streams, polls every 5 seconds until that input appears again, then asks the installed service to restart.
Output stream errors are logged, but do not currently trigger a restart.
micpipe install records the current executable path in the plist. When you
install from crates.io, that is usually the micpipe binary under Cargo's bin
directory.
Useful service commands:
micpipe status
micpipe restart
micpipe stop
micpipe start
micpipe uninstallThe service label is com.markarranz.micpipe, and the generated plist lives at:
~/Library/LaunchAgents/com.markarranz.micpipe.plist
Re-run micpipe uninstall followed by micpipe install ... when you want to
change the installed route arguments.
The service writes logs under:
~/.local/share/micpipe/out.log
~/.local/share/micpipe/err.log
Watch both logs with:
tail -f ~/.local/share/micpipe/out.log ~/.local/share/micpipe/err.logLog lines are timestamped in the user's local timezone with a numeric offset, for example:
[2026-06-29T13:04:05-07:00] Mic -> BlackHole 2ch running while output is being used as input
Human-facing lifecycle and recovery messages go to out.log. Raw stream errors
and restart-command failures go to err.log.
When the default input disconnects, out.log includes a recovery message like:
[2026-06-29T13:04:05-07:00] input device disconnected: MacBook Pro Microphone; attempting micpipe restart
When the system default input changes without a disconnect, out.log includes:
[2026-06-29T13:04:05-07:00] default input changed: MacBook Pro Microphone -> USB Microphone; attempting micpipe restart
When a pinned input disconnects, out.log records that micpipe is waiting for
that device before restarting. The running process remains alive as a reconnect
monitor, but the active audio streams are stopped:
[2026-06-29T13:04:05-07:00] input device disconnected: USB Microphone; waiting for pinned input device 'USB Microphone' to reconnect before restarting
[2026-06-29T13:04:05-07:00] audio streams stopped while waiting for pinned input reconnect
[2026-06-29T13:04:10-07:00] pinned input device reconnected: USB Microphone; attempting micpipe restart
Install BlackHole, confirm it appears in macOS audio devices, or pass a different output substring:
micpipe run --output "Your Output Device"Select a default microphone in macOS Sound settings, or pass an explicit input:
micpipe run --input "Your Microphone"On macOS, this is expected until another app actively uses the selected output as an input. Select the exact output device in the app's microphone settings and start the app's input, such as by joining a call. Merely showing the device in an audio menu may not start an input stream.
Start it again and then inspect status:
micpipe start
micpipe statusIf you are following the default input, micpipe attempts an immediate service
restart when the default changes or disconnects. If you pinned an input with
--input, reconnect that same device; micpipe polls every 5 seconds and
restarts after it appears again.
Uninstall and reinstall the service so the plist is regenerated with the new arguments:
micpipe uninstall
micpipe install --input "Your Microphone" --output "BlackHole 2ch"Run the checks before committing changes:
cargo fmt
cargo test
cargo clippy --all-targets --all-features
git diff --checkAfter changing the binary for an installed service:
cargo build --release --bin micpipe
cp target/release/micpipe ~/.local/bin/micpipe
micpipe restart
tail -f ~/.local/share/micpipe/out.log ~/.local/share/micpipe/err.logSee DESIGN.md for implementation details.