Add INI configuration and optional audio capture
This commit is contained in:
1 parent
5b15b06570
commit
089c2421c5
4 files changed
+464
-47
No files matched your search
+309
-47
@@ -20,6 +20,12 @@ param(
|
||||
[ValidatePattern("^[0-9]+[kKmM]$")]
|
||||
[string]$AudioBitrate = "192k",
|
||||
|
||||
[ValidateSet(0, 1)]
|
||||
[int]$AudioEnabled = 1,
|
||||
|
||||
[ValidateRange(0.0, 1.0)]
|
||||
[double]$AudioSignalThreshold = 0.0005,
|
||||
|
||||
[string]$NicDescription = "",
|
||||
[string]$HealthTarget = "",
|
||||
|
||||
@@ -32,8 +38,12 @@ param(
|
||||
[ValidateRange(1, 65535)]
|
||||
[int]$RtspPort = 8554,
|
||||
|
||||
[string]$ConfigPath = "",
|
||||
|
||||
[switch]$InstallStartup,
|
||||
[switch]$UninstallStartup,
|
||||
[switch]$StartupStatus,
|
||||
[switch]$ShowConfiguration,
|
||||
|
||||
[Parameter(DontShow = $true)]
|
||||
[switch]$AudioWorker,
|
||||
@@ -46,13 +56,9 @@ param(
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($EncoderPriorityCsv)) {
|
||||
$EncoderPriority = @($EncoderPriorityCsv -split ',' | ForEach-Object { $_.Trim() })
|
||||
$invalidEncoderVendor = $EncoderPriority | Where-Object { $_ -notin @("NVIDIA", "AMD", "Intel") }
|
||||
if ($invalidEncoderVendor) {
|
||||
throw "잘못된 EncoderPriorityCsv 값: $($invalidEncoderVendor -join ', ')"
|
||||
}
|
||||
$ExplicitParameters = @{}
|
||||
foreach ($parameterName in $PSBoundParameters.Keys) {
|
||||
$ExplicitParameters[$parameterName] = $true
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
@@ -485,6 +491,10 @@ namespace ScreenRtsp.Audio
|
||||
ThrowIfFailed(client.Start(), "IAudioClient.Start");
|
||||
|
||||
byte[] buffer = new byte[1024 * 1024];
|
||||
int silenceFrames = Math.Max(1, (int)(format.SamplesPerSec / 100));
|
||||
int silenceBytes = checked(silenceFrames * format.BlockAlign);
|
||||
System.Diagnostics.Stopwatch packetClock =
|
||||
System.Diagnostics.Stopwatch.StartNew();
|
||||
while (pipe.IsConnected)
|
||||
{
|
||||
uint packetFrames;
|
||||
@@ -493,7 +503,17 @@ namespace ScreenRtsp.Audio
|
||||
|
||||
if (packetFrames == 0)
|
||||
{
|
||||
Thread.Sleep(3);
|
||||
// Some endpoints stop producing loopback packets while
|
||||
// completely silent or power-saving. Keep FFmpeg and
|
||||
// RTSP clocks moving with one 10 ms silent PCM block.
|
||||
if (packetClock.ElapsedMilliseconds >= 10)
|
||||
{
|
||||
if (buffer.Length < silenceBytes) buffer = new byte[silenceBytes];
|
||||
Array.Clear(buffer, 0, silenceBytes);
|
||||
pipe.Write(buffer, 0, silenceBytes);
|
||||
packetClock.Restart();
|
||||
}
|
||||
Thread.Sleep(2);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -517,6 +537,7 @@ namespace ScreenRtsp.Audio
|
||||
try
|
||||
{
|
||||
pipe.Write(buffer, 0, bytes);
|
||||
packetClock.Restart();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -577,6 +598,180 @@ if ([string]::IsNullOrWhiteSpace($BaseDir)) {
|
||||
$BaseDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
}
|
||||
|
||||
function Read-IniConfiguration {
|
||||
param([string]$Path)
|
||||
|
||||
$result = @{}
|
||||
$sectionName = "General"
|
||||
$result[$sectionName] = @{}
|
||||
$lineNumber = 0
|
||||
|
||||
foreach ($rawLine in (Get-Content -LiteralPath $Path)) {
|
||||
$lineNumber++
|
||||
$line = $rawLine.Trim()
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($line) -or
|
||||
$line.StartsWith("#") -or
|
||||
$line.StartsWith(";") -or
|
||||
$line.StartsWith("//")) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($line -match '^\[([^\]]+)\]$') {
|
||||
$sectionName = $Matches[1].Trim()
|
||||
if (-not $result.ContainsKey($sectionName)) {
|
||||
$result[$sectionName] = @{}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ($line -notmatch '^([^=]+)=(.*)$') {
|
||||
throw "config.ini $lineNumber 번째 줄을 해석할 수 없습니다: $rawLine"
|
||||
}
|
||||
|
||||
$key = $Matches[1].Trim()
|
||||
$value = $Matches[2].Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($key)) {
|
||||
throw "config.ini $lineNumber 번째 줄의 설정 이름이 비어 있습니다."
|
||||
}
|
||||
$hasDoubleQuotes = $value.StartsWith('"') -and $value.EndsWith('"')
|
||||
$hasSingleQuotes = $value.StartsWith("'") -and $value.EndsWith("'")
|
||||
if ($value.Length -ge 2 -and ($hasDoubleQuotes -or $hasSingleQuotes)) {
|
||||
$value = $value.Substring(1, $value.Length - 2)
|
||||
}
|
||||
$result[$sectionName][$key] = $value
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
function Get-IniSetting {
|
||||
param($Configuration, [string]$Section, [string]$Key)
|
||||
if ($Configuration.ContainsKey($Section) -and $Configuration[$Section].ContainsKey($Key)) {
|
||||
return [PSCustomObject]@{ Found = $true; Value = $Configuration[$Section][$Key] }
|
||||
}
|
||||
return [PSCustomObject]@{ Found = $false; Value = $null }
|
||||
}
|
||||
|
||||
function Convert-IniSwitch {
|
||||
param([string]$Value, [string]$Name)
|
||||
if ($Value -notin @("0", "1")) {
|
||||
throw "$Name 값은 1 또는 0이어야 합니다: $Value"
|
||||
}
|
||||
return [int]$Value
|
||||
}
|
||||
|
||||
function Assert-IniSettings {
|
||||
param($Configuration)
|
||||
$allowed = @{
|
||||
General = @("ChannelCount", "StableTimeSec", "PollIntervalSec")
|
||||
Video = @("FrameRate", "Bitrate", "EncoderPriority")
|
||||
Audio = @("Enabled", "Bitrate", "SignalThreshold")
|
||||
Network = @("NicDescription", "HealthTarget")
|
||||
RTSP = @("Port")
|
||||
}
|
||||
foreach ($sectionName in $Configuration.Keys) {
|
||||
if (-not $allowed.ContainsKey($sectionName)) {
|
||||
throw "config.ini에 알 수 없는 섹션이 있습니다: [$sectionName]"
|
||||
}
|
||||
foreach ($key in $Configuration[$sectionName].Keys) {
|
||||
if ($allowed[$sectionName] -notcontains $key) {
|
||||
throw "config.ini에 알 수 없는 설정이 있습니다: [$sectionName] $key"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ConfigPath)) {
|
||||
$ConfigPath = Join-Path $BaseDir "config.ini"
|
||||
}
|
||||
elseif (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
|
||||
$ConfigPath = Join-Path $BaseDir $ConfigPath
|
||||
}
|
||||
$ConfigPath = [System.IO.Path]::GetFullPath($ConfigPath)
|
||||
|
||||
$ConfigWasLoaded = $false
|
||||
$ini = @{}
|
||||
if (Test-Path -LiteralPath $ConfigPath -PathType Leaf) {
|
||||
$ini = Read-IniConfiguration -Path $ConfigPath
|
||||
Assert-IniSettings $ini
|
||||
$ConfigWasLoaded = $true
|
||||
}
|
||||
elseif ($ExplicitParameters.ContainsKey("ConfigPath")) {
|
||||
throw "지정한 설정 파일을 찾을 수 없습니다: $ConfigPath"
|
||||
}
|
||||
|
||||
if ($ConfigWasLoaded) {
|
||||
$setting = Get-IniSetting $ini "General" "ChannelCount"
|
||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("ChannelCount")) {
|
||||
$ChannelCount = [int]$setting.Value
|
||||
}
|
||||
$setting = Get-IniSetting $ini "General" "StableTimeSec"
|
||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("StableTimeSec")) {
|
||||
$StableTimeSec = [int]$setting.Value
|
||||
}
|
||||
$setting = Get-IniSetting $ini "General" "PollIntervalSec"
|
||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("PollIntervalSec")) {
|
||||
$PollIntervalSec = [int]$setting.Value
|
||||
}
|
||||
|
||||
$setting = Get-IniSetting $ini "Video" "FrameRate"
|
||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("FrameRate")) {
|
||||
$FrameRate = [int]$setting.Value
|
||||
}
|
||||
$setting = Get-IniSetting $ini "Video" "Bitrate"
|
||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("VideoBitrate")) {
|
||||
$VideoBitrate = [string]$setting.Value
|
||||
}
|
||||
$setting = Get-IniSetting $ini "Video" "EncoderPriority"
|
||||
if ($setting.Found -and
|
||||
-not $ExplicitParameters.ContainsKey("EncoderPriority") -and
|
||||
-not $ExplicitParameters.ContainsKey("EncoderPriorityCsv")) {
|
||||
$EncoderPriority = @($setting.Value -split ',' | ForEach-Object { $_.Trim() })
|
||||
}
|
||||
|
||||
$setting = Get-IniSetting $ini "Audio" "Enabled"
|
||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("AudioEnabled")) {
|
||||
$AudioEnabled = Convert-IniSwitch $setting.Value "Audio.Enabled"
|
||||
}
|
||||
$setting = Get-IniSetting $ini "Audio" "Bitrate"
|
||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("AudioBitrate")) {
|
||||
$AudioBitrate = [string]$setting.Value
|
||||
}
|
||||
$setting = Get-IniSetting $ini "Audio" "SignalThreshold"
|
||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("AudioSignalThreshold")) {
|
||||
$AudioSignalThreshold = [double]::Parse(
|
||||
$setting.Value,
|
||||
[System.Globalization.CultureInfo]::InvariantCulture
|
||||
)
|
||||
}
|
||||
|
||||
$setting = Get-IniSetting $ini "Network" "NicDescription"
|
||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("NicDescription")) {
|
||||
$NicDescription = [string]$setting.Value
|
||||
}
|
||||
$setting = Get-IniSetting $ini "Network" "HealthTarget"
|
||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("HealthTarget")) {
|
||||
$HealthTarget = [string]$setting.Value
|
||||
}
|
||||
|
||||
$setting = Get-IniSetting $ini "RTSP" "Port"
|
||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("RtspPort")) {
|
||||
$RtspPort = [int]$setting.Value
|
||||
}
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($EncoderPriorityCsv)) {
|
||||
$EncoderPriority = @($EncoderPriorityCsv -split ',' | ForEach-Object { $_.Trim() })
|
||||
}
|
||||
$invalidEncoderVendor = $EncoderPriority | Where-Object { $_ -notin @("NVIDIA", "AMD", "Intel") }
|
||||
if ($EncoderPriority.Count -eq 0) {
|
||||
throw "EncoderPriority에는 하나 이상의 GPU 제조사가 필요합니다."
|
||||
}
|
||||
if ($invalidEncoderVendor) {
|
||||
throw "잘못된 EncoderPriority 값: $($invalidEncoderVendor -join ', ')"
|
||||
}
|
||||
|
||||
$FFmpegPath = Join-Path $BaseDir "ffmpeg.exe"
|
||||
$MediaMTXPath = Join-Path $BaseDir "mediamtx.exe"
|
||||
$MediaMTXConfig = Join-Path $BaseDir "mediamtx.yml"
|
||||
@@ -585,7 +780,6 @@ $WatchdogLog = Join-Path $LogDir "watchdog.log"
|
||||
$TaskName = "Screen RTSP Watchdog"
|
||||
$PowerShellPath = Join-Path $PSHOME "powershell.exe"
|
||||
$RTSPPublishBase = "rtsp://127.0.0.1:$RtspPort"
|
||||
$AudioSignalThreshold = 0.0005
|
||||
|
||||
function Quote-ProcessArgument {
|
||||
param([string]$Value)
|
||||
@@ -602,21 +796,8 @@ function Join-ProcessArguments {
|
||||
function Get-StartupArgumentList {
|
||||
$values = @(
|
||||
"-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $PSCommandPath,
|
||||
"-ChannelCount", $ChannelCount,
|
||||
"-FrameRate", $FrameRate,
|
||||
"-VideoBitrate", $VideoBitrate,
|
||||
"-AudioBitrate", $AudioBitrate,
|
||||
"-StableTimeSec", $StableTimeSec,
|
||||
"-PollIntervalSec", $PollIntervalSec,
|
||||
"-RtspPort", $RtspPort,
|
||||
"-EncoderPriorityCsv", ($EncoderPriority -join ",")
|
||||
"-ConfigPath", $ConfigPath
|
||||
)
|
||||
if (-not [string]::IsNullOrWhiteSpace($NicDescription)) {
|
||||
$values += @("-NicDescription", $NicDescription)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($HealthTarget)) {
|
||||
$values += @("-HealthTarget", $HealthTarget)
|
||||
}
|
||||
return Join-ProcessArguments $values
|
||||
}
|
||||
|
||||
@@ -632,6 +813,27 @@ function Install-StartupTask {
|
||||
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger `
|
||||
-Principal $principal -Settings $settings -Force | Out-Null
|
||||
Write-Host "자동 실행 작업을 등록했습니다: $TaskName"
|
||||
Write-Host "설정 파일: $ConfigPath"
|
||||
}
|
||||
|
||||
if ($ShowConfiguration) {
|
||||
[PSCustomObject]@{
|
||||
ConfigPath = $ConfigPath
|
||||
ConfigLoaded = $ConfigWasLoaded
|
||||
ChannelCount = $ChannelCount
|
||||
StableTimeSec = $StableTimeSec
|
||||
PollIntervalSec = $PollIntervalSec
|
||||
FrameRate = $FrameRate
|
||||
VideoBitrate = $VideoBitrate
|
||||
EncoderPriority = ($EncoderPriority -join " > ")
|
||||
AudioEnabled = $AudioEnabled
|
||||
AudioBitrate = $AudioBitrate
|
||||
AudioSignalThreshold = $AudioSignalThreshold
|
||||
NicDescription = $NicDescription
|
||||
HealthTarget = $HealthTarget
|
||||
RtspPort = $RtspPort
|
||||
} | Format-List
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($InstallStartup) {
|
||||
@@ -645,6 +847,25 @@ if ($UninstallStartup) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($StartupStatus) {
|
||||
$task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if (-not $task) {
|
||||
Write-Host "자동 실행 작업이 등록되어 있지 않습니다: $TaskName"
|
||||
exit 1
|
||||
}
|
||||
$taskInfo = Get-ScheduledTaskInfo -TaskName $TaskName
|
||||
[PSCustomObject]@{
|
||||
TaskName = $TaskName
|
||||
State = $task.State
|
||||
LastRunTime = $taskInfo.LastRunTime
|
||||
LastTaskResult = $taskInfo.LastTaskResult
|
||||
NextRunTime = $taskInfo.NextRunTime
|
||||
Execute = $task.Actions.Execute
|
||||
Arguments = $task.Actions.Arguments
|
||||
} | Format-List
|
||||
exit 0
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $LogDir)) {
|
||||
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
|
||||
}
|
||||
@@ -710,6 +931,9 @@ function Test-FFmpegCapabilities {
|
||||
if ($encoders -notmatch '(?m)\bh264_(nvenc|amf|qsv)\b') {
|
||||
throw "현재 FFmpeg에 지원 가능한 H.264 하드웨어 인코더가 없습니다."
|
||||
}
|
||||
if ($AudioEnabled -eq 1 -and $encoders -notmatch '(?m)\baac\b') {
|
||||
throw "오디오 캡처가 켜져 있지만 현재 FFmpeg에 AAC 인코더가 없습니다."
|
||||
}
|
||||
return $encoders
|
||||
}
|
||||
|
||||
@@ -824,7 +1048,9 @@ function Select-HardwareEncoder {
|
||||
}
|
||||
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
Initialize-AudioInterop
|
||||
if ($AudioEnabled -eq 1) {
|
||||
Initialize-AudioInterop
|
||||
}
|
||||
|
||||
$DisplayInteropSource = @'
|
||||
using System;
|
||||
@@ -1227,11 +1453,6 @@ function Start-Stream {
|
||||
$Endpoint,
|
||||
$Encoder
|
||||
)
|
||||
$pipeName = "screen-rtsp-{0}-{1}-{2}" -f $PID,$Channel,([Guid]::NewGuid().ToString("N"))
|
||||
$audioProcess = Start-AudioCaptureWorker -Channel $Channel -Endpoint $Endpoint -PipeName $pipeName
|
||||
$script:AudioProcesses["monitor$Channel"] = $audioProcess
|
||||
Wait-AudioPipe -Process $audioProcess -PipeName $pipeName
|
||||
|
||||
$ddaInput = "ddagrab=output_idx=$($Screen.OutputIndex):draw_mouse=1:framerate=${FrameRate}:dup_frames=1"
|
||||
$filters = @("hwdownload", "format=bgra")
|
||||
$rotationFilter = Get-RotationFilter $Screen.Orientation
|
||||
@@ -1240,27 +1461,48 @@ function Start-Stream {
|
||||
$videoFilter = $filters -join ","
|
||||
$rtspUrl = "$RTSPPublishBase/monitor$Channel"
|
||||
$logFile = Join-Path $LogDir "monitor$Channel-ffmpeg.log"
|
||||
$pipePath = "\\.\pipe\$pipeName"
|
||||
|
||||
$arguments = @(
|
||||
"-hide_banner", "-loglevel", "warning", "-nostdin",
|
||||
"-f", "lavfi", "-i", $ddaInput,
|
||||
"-thread_queue_size", "1024", "-f", $Endpoint.SampleFormat,
|
||||
"-ar", $Endpoint.SampleRate, "-ac", $Endpoint.Channels, "-i", $pipePath,
|
||||
"-map", "0:v:0", "-map", "1:a:0",
|
||||
"-vf", $videoFilter,
|
||||
"-c:v", $Encoder.Name
|
||||
) + $Encoder.Arguments + @(
|
||||
"-f", "lavfi", "-i", $ddaInput
|
||||
)
|
||||
|
||||
if ($AudioEnabled -eq 1) {
|
||||
$pipeName = "screen-rtsp-{0}-{1}-{2}" -f $PID,$Channel,([Guid]::NewGuid().ToString("N"))
|
||||
$audioProcess = Start-AudioCaptureWorker -Channel $Channel -Endpoint $Endpoint -PipeName $pipeName
|
||||
$script:AudioProcesses["monitor$Channel"] = $audioProcess
|
||||
Wait-AudioPipe -Process $audioProcess -PipeName $pipeName
|
||||
$pipePath = "\\.\pipe\$pipeName"
|
||||
$arguments += @(
|
||||
"-thread_queue_size", "1024", "-f", $Endpoint.SampleFormat,
|
||||
"-ar", $Endpoint.SampleRate, "-ac", $Endpoint.Channels, "-i", $pipePath,
|
||||
"-map", "0:v:0", "-map", "1:a:0"
|
||||
)
|
||||
}
|
||||
else {
|
||||
$arguments += @("-map", "0:v:0")
|
||||
}
|
||||
|
||||
$arguments += @("-vf", $videoFilter, "-c:v", $Encoder.Name)
|
||||
$arguments += $Encoder.Arguments
|
||||
$arguments += @(
|
||||
"-b:v", $VideoBitrate, "-maxrate", $VideoBitrate, "-bufsize", $VideoBitrate,
|
||||
"-g", $FrameRate, "-bf", "0", "-pix_fmt", "nv12",
|
||||
"-g", $FrameRate, "-bf", "0", "-pix_fmt", "nv12"
|
||||
)
|
||||
if ($AudioEnabled -eq 1) {
|
||||
$arguments += @(
|
||||
"-c:a", "aac", "-b:a", $AudioBitrate, "-ar", "48000", "-ac", "2",
|
||||
"-af", "aresample=async=1000:first_pts=0",
|
||||
"-af", "aresample=async=1000:first_pts=0"
|
||||
)
|
||||
}
|
||||
$arguments += @(
|
||||
"-f", "rtsp", "-rtsp_transport", "tcp", $rtspUrl
|
||||
)
|
||||
|
||||
$audioDescription = if ($AudioEnabled -eq 1) { $Endpoint.Name } else { "사용 안 함" }
|
||||
Write-Log ("monitor{0} 시작: DDA output_idx={1}, {2}x{3}, 회전={4}, {5}, 오디오={6}" -f `
|
||||
$Channel,$Screen.OutputIndex,$Screen.Width,$Screen.Height,$Screen.Orientation,
|
||||
$Encoder.Name,$Endpoint.Name)
|
||||
$Encoder.Name,$audioDescription)
|
||||
|
||||
return Start-Process -FilePath $FFmpegPath -ArgumentList (Join-ProcessArguments $arguments) `
|
||||
-WorkingDirectory $BaseDir -WindowStyle Hidden -RedirectStandardError $logFile -PassThru
|
||||
@@ -1271,7 +1513,12 @@ function Start-Streams {
|
||||
$selected = @(Select-StreamingScreens $Screens)
|
||||
if ($selected.Count -lt $ChannelCount) { return }
|
||||
Write-Log "RTSP $ChannelCount 채널 시작"
|
||||
Write-Log "오디오 출력 장치: $($Endpoint.Name) (peak=$([Math]::Round($Endpoint.Peak, 4)))"
|
||||
if ($AudioEnabled -eq 1) {
|
||||
Write-Log "오디오 출력 장치: $($Endpoint.Name) (peak=$([Math]::Round($Endpoint.Peak, 4)))"
|
||||
}
|
||||
else {
|
||||
Write-Log "오디오 캡처: 사용 안 함"
|
||||
}
|
||||
for ($index = 0; $index -lt $ChannelCount; $index++) {
|
||||
$channel = $index + 1
|
||||
$process = Start-Stream -Channel $channel -Screen $selected[$index] `
|
||||
@@ -1285,7 +1532,9 @@ function Start-Streams {
|
||||
}
|
||||
|
||||
function Test-StreamsRunning {
|
||||
if ($StreamProcesses.Count -ne $ChannelCount -or $AudioProcesses.Count -ne $ChannelCount) {
|
||||
$expectedAudioProcessCount = if ($AudioEnabled -eq 1) { $ChannelCount } else { 0 }
|
||||
if ($StreamProcesses.Count -ne $ChannelCount -or
|
||||
$AudioProcesses.Count -ne $expectedAudioProcessCount) {
|
||||
return $false
|
||||
}
|
||||
foreach ($process in @($StreamProcesses.Values) + @($AudioProcesses.Values)) {
|
||||
@@ -1308,7 +1557,10 @@ try {
|
||||
$gpuSignature = Get-GpuSignature
|
||||
|
||||
Write-Log "Screen RTSP Watchdog 시작"
|
||||
if ($ConfigWasLoaded) { Write-Log "설정 파일: $ConfigPath" }
|
||||
else { Write-Log "설정 파일 없음 - 내장 기본값 사용: $ConfigPath" }
|
||||
Write-Log "인코더 우선순위: $($EncoderPriority -join ' > ')"
|
||||
Write-Log "오디오 캡처: $(if ($AudioEnabled -eq 1) { '사용' } else { '사용 안 함' })"
|
||||
if ($NicDescription) { Write-Log "고정 NIC: $NicDescription" }
|
||||
else { Write-Log "NIC 선택: IPv4 기본 경로 자동 감지" }
|
||||
if ($HealthTarget) { Write-Log "통신 확인 대상: $HealthTarget" }
|
||||
@@ -1329,8 +1581,16 @@ try {
|
||||
}
|
||||
|
||||
$screens = @(Get-ActiveScreens)
|
||||
$endpoints = @(Get-AudioEndpoints)
|
||||
$endpoint = Select-AudioEndpoint $endpoints
|
||||
if ($AudioEnabled -eq 1) {
|
||||
$endpoints = @(Get-AudioEndpoints)
|
||||
$endpoint = Select-AudioEndpoint $endpoints
|
||||
$audioSignature = Get-AudioSignature $endpoint
|
||||
}
|
||||
else {
|
||||
$endpoints = @()
|
||||
$endpoint = $null
|
||||
$audioSignature = "AUDIO_DISABLED"
|
||||
}
|
||||
$network = Get-NetworkState
|
||||
|
||||
if (((Get-Date) - $lastGpuCheck).TotalSeconds -ge 10) {
|
||||
@@ -1346,7 +1606,7 @@ try {
|
||||
}
|
||||
|
||||
$systemSignature = (Get-ScreenSignature $screens) + "||" +
|
||||
(Get-NetworkSignature $network) + "||" + (Get-AudioSignature $endpoint) +
|
||||
(Get-NetworkSignature $network) + "||" + $audioSignature +
|
||||
"||" + $encoder.Name
|
||||
|
||||
if ($systemSignature -ne $lastSystemSignature) {
|
||||
@@ -1367,7 +1627,9 @@ try {
|
||||
$reason = "기본 D3D11 adapter에서 캡처 가능한 모니터 $ChannelCount 개 미만"
|
||||
}
|
||||
if (-not $reason) { $reason = Test-NetworkHealth $network }
|
||||
if (-not $reason -and -not $endpoint) { $reason = "활성 오디오 출력 장치 없음" }
|
||||
if (-not $reason -and $AudioEnabled -eq 1 -and -not $endpoint) {
|
||||
$reason = "활성 오디오 출력 장치 없음"
|
||||
}
|
||||
|
||||
if ($reason) {
|
||||
if ($reason -ne $lastReason) { Write-Log "송출 조건 불충족: $reason" }
|
||||
@@ -1384,7 +1646,7 @@ try {
|
||||
|
||||
if (($StreamProcesses.Count -gt 0 -or $AudioProcesses.Count -gt 0) -and
|
||||
-not (Test-StreamsRunning)) {
|
||||
Write-Log "FFmpeg 또는 오디오 loopback worker 비정상 종료 감지"
|
||||
Write-Log "FFmpeg 또는 보조 프로세스 비정상 종료 감지"
|
||||
Stop-Streams
|
||||
$healthySince = $null
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user