feat(windows-host): mic passthrough — auto-wire audio devices + bundle VB-CABLE

The Windows virtual mic worked only with manual Sound-settings fiddling: on a
headless host (no real audio output) BOTH the desktop-audio loopback and the
virtual mic must run on virtual cables, and on DIFFERENT ones or the loopback
re-captures the injected mic (echo). The Steam pair gives only one usable cable
(Steam Streaming Speakers loopback is silent — validated), so the mic + loopback
collided and echoed, and when the default playback happened to be the mic device
the anti-echo guard reported the mic "unavailable".

Host now auto-wires the devices at startup (audio/windows/audio_control.rs,
ensure_wired_once, hooked from open_audio_capture/open_virtual_mic): default
playback = a loopback-capable render that is NOT a cable and NOT the dead Steam
Speakers (real output > Steam Streaming Microphone); default recording = the mic
capture (VB-Cable "CABLE Output" preferred). Uses a hand-rolled IPolicyConfig
vtable (the only way to set a default endpoint; not in windows/wasapi crates).
Opt out with PUNKTFUNK_KEEP_DEFAULT. wasapi_mic candidates now prefer "cable
input". Validated live: from a deliberately-wrong start (playback=CABLE Input)
the host corrected both default endpoints at the OS level.

A Windows audio endpoint can only be created by a kernel-mode driver (no UMDF
path — ACX is KMDF-only), so we cannot self-sign our own like the UMDF gamepad/
display drivers. Instead the installer bundles + silently installs the official
base VB-CABLE (VB-Audio donationware, vendor-signed → loads with no test-signing,
redistributed under VB-Audio's bundling grant): install-vbcable.ps1 (seed the
VB-Audio cert into TrustedPublisher, run -i -h) + an installaudiocable task,
gated on -VbCableDir/$env:VBCABLE_DIR (the package binary is not in the repo).
Attribution in packaging/windows/licenses/VB-CABLE-NOTICE.txt. .iss compiles
with the path enabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 01:18:03 +02:00
parent 0f798d62b6
commit 83ee53290e
9 changed files with 422 additions and 5 deletions
+11
View File
@@ -65,6 +65,16 @@ read it from `%ProgramData%\punktfunk\web-password`.
- **Virtual gamepads need no prerequisite.** The DualSense / DualShock 4 / Xbox 360 (XUSB) UMDF drivers
are **bundled** in the installer (the *Install the virtual gamepad drivers* task) and
`pnputil`-installed. **ViGEmBus is no longer used.**
- **The streaming microphone uses VB-CABLE**, bundled + silently installed by the installer (the *Install
VB-CABLE virtual audio* task). The host writes the client's mic into VB-CABLE's input; its `CABLE
Output` capture endpoint surfaces as a host mic. A Windows audio device can only be created by a
**kernel-mode** driver (no UMDF path exists), so unlike our self-signed UMDF drivers we cannot ship our
own — VB-CABLE is a vendor-signed cable that loads with no test-signing. It is **donationware** by
VB-Audio, redistributed under VB-Audio's bundling grant (only the single base cable); see
`licenses/VB-CABLE-NOTICE.txt`. The package binary is **not** in the repo — supply it to the packer via
`-VbCableDir` / `$env:VBCABLE_DIR` (the extracted official package, containing `VBCABLE_Setup_x64.exe`).
Absent → the installer is built without it and the host falls back to auto-installing the Steam
Streaming pair. *(Endgame: attestation-sign our own MIT virtual-audio driver to drop this dependency.)*
## Files here
@@ -74,6 +84,7 @@ read it from `%ProgramData%\punktfunk\web-password`.
| `pack-host-installer.ps1` | Orchestrator: cert + sign exe, **build + sign the drivers from source**, stage them + FFmpeg + the **web console** (`.output` + bun) + the HDR layer, run ISCC, sign setup.exe. |
| `build-pf-vdisplay.ps1` | Build pf-vdisplay from source (the `drivers/` workspace) + clear FORCE_INTEGRITY + sign `.dll`/`.cat` + export `.cer`. |
| `build-gamepad-drivers.ps1` | Sign + catalog the gamepad drivers (`pf-dualsense` + `pf-xusb`) from the same workspace build (`-SkipBuild`), one shared cert. |
| `install-vbcable.ps1` | On-target: seed VB-Audio's cert into `TrustedPublisher`, silently install the bundled VB-CABLE (`-i -h`). Run by the installer's *Install VB-CABLE virtual audio* task; idempotent + always exits 0 (non-fatal). |
| `clear-force-integrity.ps1` | Clear the `/INTEGRITYCHECK` PE bit so a self-signed driver loads (reused by every driver build). |
| `stage-pf-vdisplay.ps1` | Stage the just-built pf-vdisplay bundle + fetch/verify the **pinned** nefcon release. |
| `../../scripts/windows/web-run.cmd` | The `PunktfunkWeb` task action: loads the mgmt token + login password env, runs the bundled `bun` on the Nitro server (`:3000`). |
+97
View File
@@ -0,0 +1,97 @@
<#
.SYNOPSIS
Silently install the bundled VB-Audio Virtual Cable (the punktfunk virtual microphone) on the host.
.DESCRIPTION
punktfunk pipes the streaming client's microphone into a virtual audio cable's render endpoint; the
cable's capture endpoint ("CABLE Output") then surfaces as a host microphone that games/apps record
from (see crates/punktfunk-host/src/audio/windows/wasapi_mic.rs). On a headless host there is no real
audio output, so a virtual cable is required. We bundle the OFFICIAL base VB-CABLE package (VB-Audio,
https://vb-cable.com) and install it unattended:
1. If a "CABLE Input"/"CABLE Output" endpoint already exists, do nothing (idempotent).
2. Pre-seed VB-Audio's Authenticode signing certificate (read from the bundled signed driver) into
LocalMachine\TrustedPublisher, so the kernel-driver-publisher prompt is suppressed and the
install is fully silent (required for the SYSTEM/Session-0 service install).
3. Run the official silent installer: VBCABLE_Setup_x64.exe -i -h (arm64: the same exe name in the
arm64 package; x86 falls back to VBCABLE_Setup.exe).
4. Wait briefly for the audio subsystem to register the new endpoint.
VB-CABLE is donationware by VB-Audio Software, redistributed here under VB-Audio's bundling grant
(https://vb-audio.com/Services/licensing.htm); see {app}\licenses\VB-CABLE-NOTICE.txt. Only the base
single cable is bundled (A+B / C+D are not redistributable).
Best-effort: any failure is logged and returns a non-zero exit, but the caller (the installer) treats
it as non-fatal — the host still runs (mic passthrough then needs a manually-installed cable, and the
host falls back to auto-installing the Steam Streaming pair).
.PARAMETER Dir
The staged VB-CABLE package directory (contains VBCABLE_Setup_x64.exe + the signed driver files).
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)][string]$Dir
)
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
function Test-CablePresent {
# An active render OR capture endpoint named "CABLE ..." means VB-CABLE is already installed.
$eps = Get-PnpDevice -Class AudioEndpoint -ErrorAction SilentlyContinue |
Where-Object { $_.Status -eq 'OK' -and $_.FriendlyName -match 'CABLE (Input|Output|In)' }
return [bool]$eps
}
if (Test-CablePresent) {
Write-Host 'VB-CABLE already installed (CABLE endpoint present) - skipping.'
exit 0
}
if (-not (Test-Path -LiteralPath $Dir)) { throw "VB-CABLE package dir not found: $Dir" }
# Pick the silent installer for this architecture. The x64 package ships both; arm64 ships an arm64
# VBCABLE_Setup_x64.exe (VB-Audio's naming); fall back to the 32-bit setup if that's all that's staged.
$setup = $null
foreach ($name in @('VBCABLE_Setup_x64.exe', 'VBCABLE_Setup.exe')) {
$p = Join-Path $Dir $name
if (Test-Path -LiteralPath $p) { $setup = $p; break }
}
if (-not $setup) { throw "no VBCABLE_Setup*.exe under $Dir" }
Write-Host "VB-CABLE silent installer: $setup"
# --- pre-seed VB-Audio's signing cert into LocalMachine\TrustedPublisher (unattended driver install) ---
# Read the Authenticode signer from a bundled signed file (prefer a driver .sys/.cat; fall back to the
# setup exe). Importing it into TrustedPublisher makes Windows install the signed driver with no prompt.
try {
$signed = Get-ChildItem -LiteralPath $Dir -Recurse -Include '*.sys', '*.cat', '*.exe' -ErrorAction SilentlyContinue |
ForEach-Object { Get-AuthenticodeSignature -LiteralPath $_.FullName -ErrorAction SilentlyContinue } |
Where-Object { $_.Status -eq 'Valid' -and $_.SignerCertificate } |
Select-Object -First 1
if ($signed -and $signed.SignerCertificate) {
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store('TrustedPublisher', 'LocalMachine')
$store.Open('ReadWrite')
$store.Add($signed.SignerCertificate)
$store.Close()
Write-Host "seeded VB-Audio cert into LocalMachine\TrustedPublisher (subject=$($signed.SignerCertificate.Subject))"
}
else {
Write-Warning 'no valid Authenticode signer found in the VB-CABLE package - the driver-publisher prompt may appear (install may stall under SYSTEM)'
}
}
catch {
Write-Warning "could not pre-seed the VB-Audio cert: $($_.Exception.Message)"
}
# --- run the official silent install: -i (install) -h (hidden) -----------------------------------
# VB-Audio documents these switches; the process returns before the endpoint is fully registered.
$proc = Start-Process -FilePath $setup -ArgumentList '-i', '-h' -Wait -PassThru -WindowStyle Hidden
Write-Host "VBCABLE setup exit code: $($proc.ExitCode)"
# Give the audio subsystem time to enumerate the new endpoint, then verify.
for ($i = 0; $i -lt 10; $i++) {
Start-Sleep -Seconds 1
if (Test-CablePresent) { Write-Host 'VB-CABLE installed - CABLE endpoint present.'; exit 0 }
}
Write-Warning 'VB-CABLE setup ran but no CABLE endpoint appeared yet (a reboot may be required).'
# Non-fatal: the device often appears after the next session/reboot; the host retries mic open with backoff.
exit 0
@@ -0,0 +1,26 @@
VB-CABLE Virtual Audio Device — Attribution
===========================================
The punktfunk host installer bundles and silently installs VB-CABLE, the virtual
audio cable used as the streaming virtual microphone (the client's mic is written
into VB-CABLE's input, and its "CABLE Output" capture endpoint surfaces as a host
microphone that games and apps record from).
VB-CABLE is a product of VB-Audio Software.
Origin: https://vb-cable.com (https://vb-audio.com)
VB-CABLE is DONATIONWARE — all participations are welcome.
Please consider donating to VB-Audio if you find it useful:
https://vb-audio.com/Cable/
VB-CABLE is redistributed here, unmodified (the official base VB-CABLE package),
under VB-Audio's distribution grant for bundling the base cable with another
application; see VB-Audio's licensing terms:
https://vb-audio.com/Services/licensing.htm
Only the single base VB-CABLE is bundled. VB-CABLE A+B and C+D are not
redistributed. VB-Audio retains all rights to VB-CABLE; punktfunk claims no
ownership of it.
To remove VB-CABLE, use its own uninstaller (VBCABLE_Setup_x64.exe -u -h) or the
"VB-Audio Virtual Cable" entry in Windows "Apps & features"; uninstalling the
punktfunk host does not remove VB-CABLE.
+24
View File
@@ -28,6 +28,7 @@ param(
[string]$FfmpegDir = $env:FFMPEG_DIR, # bundle its bin\*.dll (amf-qsv build)
[string]$WebDir = $env:WEB_OUTPUT_DIR, # built web .output tree -> bundle the mgmt console
[string]$BunExe = $env:BUN_EXE, # portable bun.exe runtime for the console
[string]$VbCableDir = $env:VBCABLE_DIR, # official base VB-CABLE package -> bundle the virtual mic
[switch]$NoDriver, # build without the bundled pf-vdisplay driver
[switch]$NoSign # skip signing (local debug)
)
@@ -189,6 +190,29 @@ if (-not $NoDriver) {
Write-Host "==> built + staged gamepad UMDF drivers -> $gpStage"
}
# --- stage the official base VB-CABLE package (the streaming virtual microphone) --------------
# VB-CABLE is the virtual audio cable the host writes the client's mic into (its capture endpoint then
# surfaces as a host microphone). We bundle + silently install the OFFICIAL base VB-CABLE package
# (VB-Audio donationware, redistributed under VB-Audio's bundling grant - see the VB-CABLE notice added
# to the licenses payload). The package binary is NOT in the repo (it's a signed third-party blob,
# shipped intact); supply it via -VbCableDir / $env:VBCABLE_DIR pointing at the extracted official
# package (must contain VBCABLE_Setup_x64.exe). Absent -> installer built WITHOUT the bundled cable; the
# host then auto-installs the Steam Streaming pair as a fallback and mic passthrough needs a manual cable.
if ($VbCableDir -and (Test-Path $VbCableDir) -and (Get-ChildItem -Path $VbCableDir -Filter 'VBCABLE_Setup*.exe' -ErrorAction SilentlyContinue)) {
$vbStage = Join-Path $OutDir 'vbcable'
if (Test-Path $vbStage) { Remove-Item -Recurse -Force $vbStage }
New-Item -ItemType Directory -Force -Path $vbStage | Out-Null
Copy-Item (Join-Path $VbCableDir '*') $vbStage -Recurse -Force
# The on-target installer script (seeds VB-Audio's cert into TrustedPublisher, runs -i -h) ships
# alongside the package so it's extracted to the same {tmp}\vbcable dir.
Copy-Item (Join-Path $here 'install-vbcable.ps1') $vbStage -Force
$defines += "/DAudioCableStageDir=$vbStage"
# Attribution: VB-Audio's bundling grant requires we surface VB-CABLE's origin + donationware status.
Copy-Item (Join-Path $here 'licenses\VB-CABLE-NOTICE.txt') -Destination $licStage -Force
Write-Host "==> bundling VB-CABLE (virtual mic) from $VbCableDir -> $vbStage"
}
else { Write-Host "no -VbCableDir/`$env:VBCABLE_DIR (or no VBCABLE_Setup*.exe in it) -> installer built WITHOUT the bundled VB-CABLE virtual mic" }
# --- stage the FFmpeg shared DLLs (AMD/Intel AMF/QSV build) ------------------------------------
# A host built with --features amf-qsv link-imports avcodec/avutil/swscale/... so the shared DLLs
# MUST sit next to the exe (it won't start otherwise). Bundle them from $FfmpegDir\bin - the same
+22
View File
@@ -41,6 +41,12 @@
#ifdef GamepadStageDir
#define WithGamepad
#endif
; AudioCableStageDir (the official base VB-CABLE package + install-vbcable.ps1) is optional - present
; when the VB-CABLE package was supplied to the packer. It is the streaming virtual microphone; on a
; headless host (no real audio output) a virtual cable is required for mic + desktop-audio passthrough.
#ifdef AudioCableStageDir
#define WithAudioCable
#endif
; FfmpegBin (a dir of FFmpeg shared DLLs) is optional - present when the host is built with
; --features amf-qsv (the AMD/Intel AMF/QSV encode backend link-imports the FFmpeg libs).
#ifdef FfmpegBin
@@ -93,6 +99,9 @@ Name: "installdriver"; Description: "Install the pf-vdisplay virtual display dri
#ifdef WithGamepad
Name: "installgamepad"; Description: "Install the virtual gamepad drivers (DualSense / DualShock 4 / Xbox 360 - no ViGEmBus needed)"
#endif
#ifdef WithAudioCable
Name: "installaudiocable"; Description: "Install VB-CABLE virtual audio (microphone passthrough - VB-Audio donationware, www.vb-cable.com)"
#endif
#ifdef WithVkLayer
Name: "installhdrlayer"; Description: "Install the HDR Vulkan layer (lets Vulkan games like Doom use HDR on the virtual display)"
#endif
@@ -132,6 +141,10 @@ Source: "{#StageDir}\*"; DestDir: "{tmp}\pfvdisplay"; Flags: deleteafterinstall
; The built-from-source UMDF gamepad drivers + install-gamepad-drivers.ps1, extracted to {tmp}, removed after.
Source: "{#GamepadStageDir}\*"; DestDir: "{tmp}\gamepad"; Flags: deleteafterinstall recursesubdirs createallsubdirs; Tasks: installgamepad
#endif
#ifdef WithAudioCable
; The official base VB-CABLE package + install-vbcable.ps1, extracted to {tmp}, removed after install.
Source: "{#AudioCableStageDir}\*"; DestDir: "{tmp}\vbcable"; Flags: deleteafterinstall recursesubdirs createallsubdirs; Tasks: installaudiocable
#endif
#ifdef WithVkLayer
; The HDR Vulkan implicit layer (cdylib + its JSON manifest) laid into {app}\vklayer and registered
; below. The manifest's library_path is ".\pf_vkhdr_layer.dll" (relative to the JSON), so the two
@@ -160,6 +173,15 @@ Filename: "{app}\punktfunk-host.exe"; Parameters: "driver install --gamepad --di
StatusMsg: "Installing the virtual gamepad drivers..."; \
Flags: runhidden waituntilterminated; Tasks: installgamepad
#endif
#ifdef WithAudioCable
; Silently install the bundled VB-CABLE (the streaming virtual microphone). Best-effort: install-vbcable.ps1
; always exits 0 (a missing cable just disables mic passthrough; the host falls back + retries), so a
; cable hiccup never fails the whole install.
Filename: "powershell.exe"; \
Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{tmp}\vbcable\install-vbcable.ps1"" -Dir ""{tmp}\vbcable"""; \
StatusMsg: "Installing VB-CABLE virtual audio (microphone passthrough)..."; \
Flags: runhidden waituntilterminated; Tasks: installaudiocable
#endif
; Register (or re-point, on upgrade - idempotent) the SYSTEM service from its FINAL {app} location:
; service install records current_exe() as the SCM binPath, so it must run from {app}, not {tmp}.
Filename: "{app}\punktfunk-host.exe"; Parameters: "service install"; WorkingDir: "{app}"; \