From 477d421dbf48d32ddbf89d7be4b3a979bd56a97a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 1 Jun 2026 01:56:19 +0100 Subject: [PATCH] Add TCP tunnel fallback for stream proof --- docs/self-hosted-live-edge.md | 16 +++++++++++ scripts/record-stream-proof-output.ps1 | 38 +++++++++++++++++++++----- scripts/start-raven-av-sender.ps1 | 18 ++++++++++-- scripts/wasapi-loopback-capture.ps1 | 9 ++++-- 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/docs/self-hosted-live-edge.md b/docs/self-hosted-live-edge.md index a01e8f0..b76335c 100644 --- a/docs/self-hosted-live-edge.md +++ b/docs/self-hosted-live-edge.md @@ -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: diff --git a/scripts/record-stream-proof-output.ps1 b/scripts/record-stream-proof-output.ps1 index dc56a0e..7cd24a7 100644 --- a/scripts/record-stream-proof-output.ps1 +++ b/scripts/record-stream-proof-output.ps1 @@ -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." } diff --git a/scripts/start-raven-av-sender.ps1 b/scripts/start-raven-av-sender.ps1 index 190354b..43c311e 100644 --- a/scripts/start-raven-av-sender.ps1 +++ b/scripts/start-raven-av-sender.ps1 @@ -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" diff --git a/scripts/wasapi-loopback-capture.ps1 b/scripts/wasapi-loopback-capture.ps1 index 2ac7a66..7499ed5 100644 --- a/scripts/wasapi-loopback-capture.ps1 +++ b/scripts/wasapi-loopback-capture.ps1 @@ -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);