Repository navigation
Add a loopback input on macOS through a process tap - #86
Merged
Merged
Conversation
--input loopback captures everything the Mac plays, and loopback:<device> one output device, through a CoreAudio process tap read by the existing AUHAL capture path via a private aggregate device. Both taps leave out sendspin-cli's own playback. macOS 14.2 or later is required at runtime, and older SDKs still build without it. A refused System Audio Recording permission yields silence, so the start-up log says where to grant it and a tap that delivers only silence logs one warning.
A failed open now releases its tap at once, the tap's format is watched for a rate change on the tapped output, and errors and logs name the loopback as typed rather than its private aggregate device.
chrisuthe
added this pull request to stack #87
October 15, 2026 18:47
Capturing a loopback driver that the player also plays into sends the server its own audio back, so the player logs one warning at startup.
chrisuthe
marked this pull request as ready for review
October 15, 2026 18:49
…dio-and' into chrisuthe/task/add-a-loopback-input-on-macos # Conflicts: # README.md # docs/wiki/Configuration.md # src/audio_source.cpp
Base automatically changed from
chrisuthe/task/add-a-loopback-input-on-pipewire-pulseaudio-and
to
main
October 15, 2026 19:08
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Failed self-exclusion permits feedback, and tap-format notifications force reopening even when the capture format remains compatible.
2 open findings
What changed in this PR
Extends loopback capture to macOS using CoreAudio process taps within the existing audio-source backend.
Changes:
- Adds system-wide and per-output loopback capture with self-playback exclusion.
- Adds permission guidance, silence detection, and virtual-device feedback warnings.
- Updates build integration, tests, and documentation.
| File | Description |
|---|---|
| tests/audio_source_test.cpp | Updates loopback availability and warning tests. |
| src/main.cpp | Warns about virtual-device feedback. |
| src/coreaudio_tap.mm | Implements process taps and aggregate-device lifecycle. |
| src/coreaudio_tap.h | Declares the process-tap interface. |
| src/coreaudio_source.h | Adds loopback state and helpers. |
| src/coreaudio_source.cpp | Integrates tap capture and silence detection. |
| src/coreaudio_device.h | Adds custom device-error labels. |
| src/coreaudio_device.cpp | Uses supplied device-error labels. |
| src/cli.cpp | Enables loopback help on macOS. |
| src/audio_source.h | Expands loopback availability and warning interfaces. |
| src/audio_source.cpp | Selects CoreAudio loopback and lists outputs. |
| README.md | Documents platform-specific playback exclusion. |
| packaging/sendspin-cli.Info.plist.in | Adds system-audio capture permission text. |
| docs/wiki/Configuration.md | Documents macOS loopback selectors. |
| docs/wiki/Advanced-Usage.md | Explains macOS capture and permissions. |
| CMakeLists.txt | Builds the Objective-C++ tap implementation. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…-format listener The aggregate's own rate listener already catches a tapped output's rate change, so watching the tap's format only added a second path to the same reopen.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Stacked on #85. Merge that one first, then retarget this PR to main.
On macOS 14.2 or later,
--input loopbackcaptures everything the Mac plays, andloopback:<index|name>captures one output device, named the way-o coreaudio:names it. Both use a CoreAudio process tap that leaves out sendspin-cli's own playback, so a player on the same Mac is never sent back to the server. That is also why macOS gets no feedback warning.Rather than adding a separate capture source,
CoreAudioSourcegets a loopback mode. It points its existing AUHAL input at a private aggregate device that holds the tap. The tap and aggregate are created and destroyed in a smallcoreaudio_tap.mm. Older macOS gets a clear error, and SDKs older than 14.2 still build.macOS has no public API to ask for or check System Audio Recording permission, and a refusal just gives silence. So the start-up log says where to grant it (and
tccutil reset AudioCaptureto be asked again), and a tap that delivers only silence for 10 s logs one warning. A tap with nothing playing delivers no audio at all, so a quiet Mac does not set off the warning.NSAudioCaptureUsageDescriptionis added to the embedded Info.plist.Tested on macOS 26 from Terminal:
loopback:<speakers>captures, and an idle second output stays silent.ctest passes on Linux and macOS.
Part of Sendspin/backlog#72