Scaffold local OBS streaming bridge
This commit is contained in:
commit
beac84ae23
18 changed files with 1159 additions and 0 deletions
33
scripts/sender-discover.ps1
Normal file
33
scripts/sender-discover.ps1
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
param(
|
||||
[string]$FfmpegPath = "ffmpeg"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Invoke-Checked {
|
||||
param(
|
||||
[string]$Label,
|
||||
[string[]]$Arguments
|
||||
)
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "== $Label =="
|
||||
& $FfmpegPath @Arguments 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "$Label exited with code $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "FFmpeg binary: $FfmpegPath"
|
||||
|
||||
Invoke-Checked -Label "Version" -Arguments @("-hide_banner", "-version")
|
||||
Invoke-Checked -Label "SRT protocol check" -Arguments @("-hide_banner", "-protocols")
|
||||
Invoke-Checked -Label "NVENC encoder check" -Arguments @("-hide_banner", "-encoders")
|
||||
Invoke-Checked -Label "DirectShow devices" -Arguments @("-hide_banner", "-f", "dshow", "-list_devices", "true", "-i", "dummy")
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Look for:"
|
||||
Write-Host "- protocol list contains srt"
|
||||
Write-Host "- encoder list contains h264_nvenc"
|
||||
Write-Host "- audio device names you can copy into config/localcast.json"
|
||||
|
||||
132
scripts/sender-start.ps1
Normal file
132
scripts/sender-start.ps1
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
param(
|
||||
[string]$Config = ".\config\localcast.json",
|
||||
[string]$FfmpegPath = "ffmpeg",
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Resolve-ConfigPath {
|
||||
param([string]$Path)
|
||||
$resolved = Resolve-Path -LiteralPath $Path -ErrorAction Stop
|
||||
return $resolved.Path
|
||||
}
|
||||
|
||||
function Assert-Value {
|
||||
param(
|
||||
[object]$Value,
|
||||
[string]$Message
|
||||
)
|
||||
if ($null -eq $Value -or "$Value".Trim().Length -eq 0) {
|
||||
throw $Message
|
||||
}
|
||||
}
|
||||
|
||||
function Get-SrtUrl {
|
||||
param(
|
||||
[object]$Receiver,
|
||||
[int]$PortOffset
|
||||
)
|
||||
|
||||
$port = [int]$Receiver.basePort + $PortOffset
|
||||
$query = "mode=caller&latency=$($Receiver.srtLatencyMicros)"
|
||||
if ($Receiver.passphrase -and "$($Receiver.passphrase)".Length -gt 0) {
|
||||
$escaped = [uri]::EscapeDataString("$($Receiver.passphrase)")
|
||||
$query = "$query&passphrase=$escaped&pbkeylen=16"
|
||||
}
|
||||
return "srt://$($Receiver.host):$($port)?$query"
|
||||
}
|
||||
|
||||
function Format-CommandLine {
|
||||
param(
|
||||
[string]$Executable,
|
||||
[string[]]$Arguments
|
||||
)
|
||||
$parts = @($Executable)
|
||||
foreach ($arg in $Arguments) {
|
||||
if ($arg -match '[\s"&?=]') {
|
||||
$escaped = $arg.Replace('"', '\"')
|
||||
$parts += '"' + $escaped + '"'
|
||||
} else {
|
||||
$parts += $arg
|
||||
}
|
||||
}
|
||||
return ($parts -join " ")
|
||||
}
|
||||
|
||||
function Start-SourceProcess {
|
||||
param(
|
||||
[string]$Name,
|
||||
[string[]]$Arguments
|
||||
)
|
||||
|
||||
$line = Format-CommandLine -Executable $FfmpegPath -Arguments $Arguments
|
||||
if ($DryRun) {
|
||||
Write-Host ""
|
||||
Write-Host "[$Name]"
|
||||
Write-Host $line
|
||||
return
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force ".\logs" | Out-Null
|
||||
$stdout = Join-Path ".\logs" "$Name.out.log"
|
||||
$stderr = Join-Path ".\logs" "$Name.err.log"
|
||||
$process = Start-Process -FilePath $FfmpegPath -ArgumentList $Arguments -RedirectStandardOutput $stdout -RedirectStandardError $stderr -PassThru -WindowStyle Hidden
|
||||
Write-Host "$Name started. PID=$($process.Id) stdout=$stdout stderr=$stderr"
|
||||
}
|
||||
|
||||
$configPath = Resolve-ConfigPath -Path $Config
|
||||
$settings = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json
|
||||
|
||||
Assert-Value -Value $settings.receiver.host -Message "receiver.host is required"
|
||||
Assert-Value -Value $settings.receiver.basePort -Message "receiver.basePort is required"
|
||||
if (-not $settings.receiver.srtLatencyMicros) {
|
||||
$settings.receiver | Add-Member -NotePropertyName srtLatencyMicros -NotePropertyValue 120000
|
||||
}
|
||||
|
||||
if ($settings.video -and $settings.video.enabled) {
|
||||
$videoUrl = Get-SrtUrl -Receiver $settings.receiver -PortOffset ([int]$settings.video.portOffset)
|
||||
$videoArgs = @(
|
||||
"-hide_banner",
|
||||
"-loglevel", "info",
|
||||
"-f", "gdigrab",
|
||||
"-framerate", "$($settings.video.framerate)",
|
||||
"-video_size", "$($settings.video.width)x$($settings.video.height)",
|
||||
"-i", "$($settings.video.source)",
|
||||
"-an",
|
||||
"-c:v", "h264_nvenc",
|
||||
"-preset", "$($settings.video.preset)",
|
||||
"-tune", "$($settings.video.tune)",
|
||||
"-b:v", "$($settings.video.bitrate)",
|
||||
"-maxrate", "$($settings.video.maxrate)",
|
||||
"-bufsize", "$($settings.video.bufsize)",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-g", "$([int]$settings.video.framerate * 2)",
|
||||
"-f", "mpegts",
|
||||
$videoUrl
|
||||
)
|
||||
Start-SourceProcess -Name "video" -Arguments $videoArgs
|
||||
}
|
||||
|
||||
foreach ($audio in @($settings.audioSources)) {
|
||||
Assert-Value -Value $audio.name -Message "audioSources[].name is required"
|
||||
Assert-Value -Value $audio.device -Message "audioSources[].device is required"
|
||||
$audioUrl = Get-SrtUrl -Receiver $settings.receiver -PortOffset ([int]$audio.portOffset)
|
||||
$audioArgs = @(
|
||||
"-hide_banner",
|
||||
"-loglevel", "info",
|
||||
"-f", "dshow",
|
||||
"-i", "audio=$($audio.device)",
|
||||
"-vn",
|
||||
"-c:a", "$(if ($audio.codec) { $audio.codec } else { "aac" })",
|
||||
"-b:a", "$($audio.bitrate)",
|
||||
"-f", "mpegts",
|
||||
$audioUrl
|
||||
)
|
||||
Start-SourceProcess -Name "audio-$($audio.name)" -Arguments $audioArgs
|
||||
}
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Host ""
|
||||
Write-Host "Dry run complete. Start OBS Media Sources in listener mode before running without -DryRun."
|
||||
}
|
||||
28
scripts/sender-stop.ps1
Normal file
28
scripts/sender-stop.ps1
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
param(
|
||||
[string]$LogDirectory = ".\logs"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$logs = Resolve-Path -LiteralPath $LogDirectory -ErrorAction SilentlyContinue
|
||||
if (-not $logs) {
|
||||
Write-Host "No log directory found at $LogDirectory"
|
||||
return
|
||||
}
|
||||
|
||||
$ffmpegProcesses = Get-CimInstance Win32_Process |
|
||||
Where-Object {
|
||||
$_.Name -ieq "ffmpeg.exe" -and
|
||||
$_.CommandLine -like "*srt://*"
|
||||
}
|
||||
|
||||
if (-not $ffmpegProcesses) {
|
||||
Write-Host "No LocalCastBridge-looking ffmpeg SRT processes found."
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($process in $ffmpegProcesses) {
|
||||
Stop-Process -Id $process.ProcessId -Force
|
||||
Write-Host "Stopped ffmpeg PID=$($process.ProcessId)"
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue