Add TCP tunnel fallback for stream proof

This commit is contained in:
Meta Vens 2026-06-01 01:56:19 +01:00
commit 477d421dbf
4 changed files with 69 additions and 12 deletions

View file

@ -97,6 +97,22 @@ powershell -ExecutionPolicy Bypass -File .\scripts\record-stream-proof-output.ps
-MicOffsetSeconds 0.000
```
If Windows firewall blocks direct SRT/UDP during a local proof, run the Raven
leg through an SSH TCP tunnel instead:
```powershell
Start-Process ssh.exe -ArgumentList '-o ExitOnForwardFailure=yes -N -L 6204:127.0.0.1:5204 -l "madman''s lullaby" 192.168.1.84' -WindowStyle Hidden
powershell -ExecutionPolicy Bypass -File .\scripts\record-stream-proof-output.ps1 `
-StartRavenSender `
-RavenSenderListens `
-RavenSenderTransport tcp-listener `
-RavenInputOverride tcp://127.0.0.1:6204
```
That fallback keeps the proof private to the LAN/SSH path and avoids changing
machine firewall policy just to capture a judging artifact.
## Scaling Rule
Viewer capacity is approximately:

View file

@ -10,8 +10,13 @@ param(
[string]$FfmpegPath = "ffmpeg",
[switch]$StartRavenSender,
[string]$RavenHost = "madman's lullaby@192.168.1.84",
[string]$RavenAddress = "192.168.1.84",
[string]$RavenRepo = "C:\Meta\Mimir",
[string]$StarfireHost = "192.168.1.66",
[switch]$RavenSenderListens,
[ValidateSet("srt", "tcp-listener")]
[string]$RavenSenderTransport = "srt",
[string]$RavenInputOverride = "",
[string]$KiyoVideoDevice = "USB Video Device",
[string]$StarfireScarlettAudioDevice = "Analogue 1 + 2 (2- Focusrite USB Audio)",
[int]$KiyoWidth = 640,
@ -28,6 +33,18 @@ param(
$ErrorActionPreference = "Stop"
function Quote-ProcessArgument([string]$Value) {
if ($Value.Length -eq 0) {
return '""'
}
if ($Value -notmatch '[\s"]') {
return $Value
}
return '"' + $Value.Replace('"', '\"') + '"'
}
$repo = Split-Path -Parent $PSScriptRoot
$absoluteOutputPath = if ([System.IO.Path]::IsPathRooted($OutputPath)) {
$OutputPath
@ -46,13 +63,21 @@ $timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$stdoutLog = Join-Path $absoluteLogRoot "stream-proof-record-$timestamp.out.log"
$stderrLog = Join-Path $absoluteLogRoot "stream-proof-record-$timestamp.err.log"
$ravenEndpoint = "srt://0.0.0.0:${RavenPort}?mode=listener&latency=120000&timeout=5000000"
$ravenEndpoint = if ($RavenInputOverride.Length -gt 0) {
$RavenInputOverride
} elseif ($RavenSenderListens) {
"srt://${RavenAddress}:${RavenPort}?mode=caller&latency=120000&timeout=30000000"
} else {
"srt://0.0.0.0:${RavenPort}?mode=listener&latency=120000&timeout=30000000"
}
$ravenSender = "${RavenRepo}\scripts\start-raven-av-sender.ps1"
if ($StartRavenSender) {
$taskName = "MimirRavenAvSender$RavenPort"
$remoteCmd = "${RavenRepo}\scripts\run-raven-av-$RavenPort.cmd"
$remoteCommand = "powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ravenSender -TargetHost $StarfireHost -Port $RavenPort"
$remoteTargetHost = if ($RavenSenderListens) { "0.0.0.0" } else { $StarfireHost }
$remoteSrtMode = if ($RavenSenderListens) { "listener" } else { "caller" }
$remoteCommand = "powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ravenSender -TargetHost $remoteTargetHost -Port $RavenPort -SrtMode $remoteSrtMode -Transport $RavenSenderTransport"
$taskTime = (Get-Date).AddMinutes(1).ToString("HH:mm")
Write-Host "Starting Raven muxed A/V sender on ${RavenHost} through interactive task ${taskName}: $remoteCommand"
if (-not $DryRun) {
@ -134,6 +159,7 @@ $arguments += @(
Write-Host "Stream-proof recording:"
Write-Host " Raven input: $ravenEndpoint"
Write-Host " Raven SRT mode: $(if ($RavenSenderListens) { 'Raven listener, Starfire caller' } else { 'Starfire listener, Raven caller' })"
Write-Host " Kiyo PIP: $KiyoVideoDevice ${KiyoWidth}x${KiyoHeight}@$KiyoFrameRate -> ${KiyoCornerWidth}x${scaledKiyoHeight}"
Write-Host " Raven audio: Realtek/default render loopback in muxed NVENC/SRT feed"
Write-Host " Starfire Scarlett: $(if ($NoStarfireScarlett) { 'disabled' } else { $StarfireScarlettAudioDevice })"
@ -153,9 +179,7 @@ $startInfo.FileName = $FfmpegPath
$startInfo.UseShellExecute = $false
$startInfo.RedirectStandardOutput = $true
$startInfo.RedirectStandardError = $true
foreach ($argument in $arguments) {
[void]$startInfo.ArgumentList.Add($argument)
}
$startInfo.Arguments = ($arguments | ForEach-Object { Quote-ProcessArgument $_ }) -join " "
$process = [System.Diagnostics.Process]::Start($startInfo)
if (-not $process) {
@ -170,8 +194,8 @@ if (-not $process.WaitForExit($timeoutMs)) {
throw "FFmpeg recorder timed out after $($DurationSeconds + $StartupTimeoutSeconds)s. Check $stderrLog."
}
$stdoutTask.GetAwaiter().GetResult()
$stderrTask.GetAwaiter().GetResult()
[void]$stdoutTask.GetAwaiter().GetResult()
[void]$stderrTask.GetAwaiter().GetResult()
if ($process.ExitCode -ne 0) {
throw "FFmpeg recorder failed with exit code $($process.ExitCode). Check $stderrLog."
}

View file

@ -6,6 +6,10 @@ param(
[int]$Framerate = 30,
[int]$AudioSampleRate = 48000,
[int]$AudioChannels = 2,
[ValidateSet("srt", "tcp-listener")]
[string]$Transport = "srt",
[ValidateSet("caller", "listener")]
[string]$SrtMode = "caller",
[string]$FfmpegPath = "ffmpeg",
[string]$Source = "desktop",
[string]$VideoBitrate = "12000k",
@ -25,7 +29,11 @@ $repo = Split-Path -Parent $PSScriptRoot
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$stdoutLog = Join-Path $LogRoot "raven-av-srt-$timestamp.out.log"
$stderrLog = Join-Path $LogRoot "raven-av-srt-$timestamp.err.log"
$endpoint = "srt://${TargetHost}:${Port}?mode=caller&latency=120000"
$endpoint = if ($Transport -eq "tcp-listener") {
"tcp://${TargetHost}:${Port}?listen=1"
} else {
"srt://${TargetHost}:${Port}?mode=${SrtMode}&latency=120000&timeout=30000000"
}
$videoSize = "${Width}x${Height}"
$gop = [Math]::Max(1, $Framerate * 2)
$captureScript = Join-Path $repo "scripts\wasapi-loopback-capture.ps1"
@ -61,8 +69,11 @@ $ffmpegArgs = @(
$endpoint
)
$quotedFfmpegArgs = $ffmpegArgs | ForEach-Object { Quote-CmdArgument $_ }
$command = (Quote-CmdArgument $captureScript) +
" -Output - -SampleRate $AudioSampleRate -Channels $AudioChannels | " +
$command = "powershell.exe -NoProfile -ExecutionPolicy Bypass -File " +
(Quote-CmdArgument $captureScript) +
" -Output " +
(Quote-CmdArgument "stdout") +
" -SampleRate $AudioSampleRate -Channels $AudioChannels | " +
(Quote-CmdArgument $FfmpegPath) +
" " +
($quotedFfmpegArgs -join " ")
@ -70,6 +81,7 @@ $command = (Quote-CmdArgument $captureScript) +
Write-Host "Raven muxed A/V sender:"
Write-Host " ffmpeg: $FfmpegPath"
Write-Host " target: $endpoint"
Write-Host " transport: $Transport"
Write-Host " video: $Source $videoSize@$Framerate via h264_nvenc"
Write-Host " audio: Raven Realtek/default render loopback ${AudioChannels}ch ${AudioSampleRate}Hz -> AAC"
Write-Host " sync role: Raven Realtek is co-streamer game/program loopback packaged with NVENC; Starfire Realtek owns chirp emission"

View file

@ -112,7 +112,7 @@ namespace Mimir {
}
public static void Capture(string outputPath, double seconds, int targetRate, int targetChannels, string roleName) {
Stream output = outputPath == "-" ? Console.OpenStandardOutput() : new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.Read);
Stream output = IsStdoutPath(outputPath) ? Console.OpenStandardOutput() : new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.Read);
try {
IMMDeviceEnumerator enumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
IMMDevice device;
@ -216,10 +216,15 @@ namespace Mimir {
}
}
finally {
if (outputPath != "-") output.Dispose();
if (!IsStdoutPath(outputPath)) output.Dispose();
}
}
static bool IsStdoutPath(string outputPath) {
return String.Equals(outputPath, "-", StringComparison.Ordinal) ||
String.Equals(outputPath, "stdout", StringComparison.OrdinalIgnoreCase);
}
static void Check(int hr, string stage) {
if (hr < 0) {
Exception inner = Marshal.GetExceptionForHR(hr);