Recover stalled RTSP streams
This commit is contained in:
1 parent
4adf42cc4c
commit
4cfce2ea2d
3 files changed
+205
-2
No files matched your search
@@ -40,11 +40,12 @@ RTSP는 `ddagrab` 기반 Desktop Duplication API로 화면과 마우스 커서
|
|||||||
- 기본 실행과 예약 작업 모두 PowerShell 창을 표시하지 않는 숨김 백그라운드 실행
|
- 기본 실행과 예약 작업 모두 PowerShell 창을 표시하지 않는 숨김 백그라운드 실행
|
||||||
- 절전 해제나 장시간 polling 중단 감지 시 전체 채널 강제 재구성
|
- 절전 해제나 장시간 polling 중단 감지 시 전체 채널 강제 재구성
|
||||||
- 로그인 트리거와 1분 간격 무기한 복구 트리거를 이용한 Watchdog 지속 복구
|
- 로그인 트리거와 1분 간격 무기한 복구 트리거를 이용한 Watchdog 지속 복구
|
||||||
|
- 각 RTSP 채널에서 실제 영상 프레임을 주기적으로 디코딩해 살아 있는 채 멈춘 FFmpeg도 자동 복구
|
||||||
- `config.ini` 기반 기능별 설정과 명령줄 override
|
- `config.ini` 기반 기능별 설정과 명령줄 override
|
||||||
- 전체 오디오와 마이크 혼합을 각각 `1` 또는 `0`으로 활성화·비활성화
|
- 전체 오디오와 마이크 혼합을 각각 `1` 또는 `0`으로 활성화·비활성화
|
||||||
- FFmpeg와 MediaMTX 부재 시 HTTPS 다운로드, 게시 SHA-256 검증, 후보 선검증 및 백업 가능한 안전 교체
|
- FFmpeg와 MediaMTX 부재 시 HTTPS 다운로드, 게시 SHA-256 검증, 후보 선검증 및 백업 가능한 안전 교체
|
||||||
- `config.ini` 최상단에 비개인 환경·호환성 정보를 원자적으로 자동 기록
|
- `config.ini` 최상단에 비개인 환경·호환성 정보를 원자적으로 자동 기록
|
||||||
- 모니터·오디오·NIC·프로세스·절전 복구·설정·안전장치용 내장 시뮬레이션 49건
|
- 모니터·오디오·NIC·프로세스·RTSP 실제 프레임·절전 복구·설정·안전장치용 내장 시뮬레이션 51건
|
||||||
|
|
||||||
## RTSP URL
|
## RTSP URL
|
||||||
|
|
||||||
@@ -139,6 +140,9 @@ SignalThreshold=0.0005
|
|||||||
| `Network` | `NicDescription` | 빈 값 | 비우면 기본 경로 NIC 자동 선택 |
|
| `Network` | `NicDescription` | 빈 값 | 비우면 기본 경로 NIC 자동 선택 |
|
||||||
| `Network` | `HealthTarget` | 빈 값 | Source IP ping 대상 |
|
| `Network` | `HealthTarget` | 빈 값 | Source IP ping 대상 |
|
||||||
| `RTSP` | `Port` | `8554` | MediaMTX RTSP 포트 |
|
| `RTSP` | `Port` | `8554` | MediaMTX RTSP 포트 |
|
||||||
|
| `RTSP` | `HealthCheckIntervalSec` | `10` | 모든 RTSP 채널의 실제 프레임 검사 주기 |
|
||||||
|
| `RTSP` | `HealthTimeoutSec` | `5` | 새 수신기가 영상 1프레임을 디코딩할 제한 시간 |
|
||||||
|
| `RTSP` | `HealthFailureThreshold` | `2` | 전체 AV 재시작 전 허용할 연속 검사 실패 횟수 |
|
||||||
| `Dependencies` | `AutoInstall` | `1` | 실행 파일 부재 시 자동 설치 여부 |
|
| `Dependencies` | `AutoInstall` | `1` | 실행 파일 부재 시 자동 설치 여부 |
|
||||||
| `Dependencies` | `FFmpegUrl` | Gyan essentials ZIP | FFmpeg Windows ZIP 다운로드 주소 |
|
| `Dependencies` | `FFmpegUrl` | Gyan essentials ZIP | FFmpeg Windows ZIP 다운로드 주소 |
|
||||||
| `Dependencies` | `FFmpegSha256Url` | Gyan SHA-256 | FFmpeg 게시 해시 주소 |
|
| `Dependencies` | `FFmpegSha256Url` | Gyan SHA-256 | FFmpeg 게시 해시 주소 |
|
||||||
@@ -213,6 +217,7 @@ INI를 해석한 최종 설정값만 확인하고 종료할 수도 있습니다.
|
|||||||
9. 화면은 `ddagrab -> hwdownload -> BGRA -> 회전 보정 -> NV12 -> 하드웨어 H.264` 순서로 처리합니다.
|
9. 화면은 `ddagrab -> hwdownload -> BGRA -> 회전 보정 -> NV12 -> 하드웨어 H.264` 순서로 처리합니다.
|
||||||
10. 시스템 소리는 named pipe, 마이크는 DirectShow로 FFmpeg에 입력됩니다. 두 소리를 `amix`한 뒤 `asplit`으로 각 RTSP 출력에 넣고 AAC로 인코딩합니다.
|
10. 시스템 소리는 named pipe, 마이크는 DirectShow로 FFmpeg에 입력됩니다. 두 소리를 `amix`한 뒤 `asplit`으로 각 RTSP 출력에 넣고 AAC로 인코딩합니다.
|
||||||
11. 모니터, NIC, GPU, 드라이버, 실행 파일, 오디오 장치 또는 자식 프로세스 상태가 바뀌면 전체 채널을 종료한 뒤 검증과 구성을 새로 읽어 재시작합니다.
|
11. 모니터, NIC, GPU, 드라이버, 실행 파일, 오디오 장치 또는 자식 프로세스 상태가 바뀌면 전체 채널을 종료한 뒤 검증과 구성을 새로 읽어 재시작합니다.
|
||||||
|
12. FFmpeg PID가 살아 있어도 각 RTSP URL에 새 로컬 수신기를 붙여 실제 영상 프레임을 확인하며, 연속 실패하면 단일 FFmpeg와 오디오 worker를 함께 재시작합니다.
|
||||||
|
|
||||||
## 설치 및 실행
|
## 설치 및 실행
|
||||||
|
|
||||||
@@ -247,6 +252,8 @@ Start-ScheduledTask -TaskName "Screen RTSP Watchdog"
|
|||||||
|
|
||||||
Watchdog 내부에서는 Windows `PowerModeChanged/Resume` 이벤트를 감시하고, 이벤트 누락에 대비해 polling 시간 공백도 함께 확인합니다. 절전 해제가 감지되면 단일 FFmpeg AV 파이프라인을 종료하고 모니터·NIC·오디오·GPU 구성을 다시 읽은 뒤 안정화 시간 이후 재시작합니다. 모니터 분리 시에는 채널을 종료하고 계속 대기하며, 필요한 수만큼 다시 연결되면 동일하게 자동 복구합니다.
|
Watchdog 내부에서는 Windows `PowerModeChanged/Resume` 이벤트를 감시하고, 이벤트 누락에 대비해 polling 시간 공백도 함께 확인합니다. 절전 해제가 감지되면 단일 FFmpeg AV 파이프라인을 종료하고 모니터·NIC·오디오·GPU 구성을 다시 읽은 뒤 안정화 시간 이후 재시작합니다. 모니터 분리 시에는 채널을 종료하고 계속 대기하며, 필요한 수만큼 다시 연결되면 동일하게 자동 복구합니다.
|
||||||
|
|
||||||
|
프로세스 생존 여부와 실제 송출 상태는 별도로 검사합니다. `HealthCheckIntervalSec`마다 모든 `monitorN` URL에 숨김 로컬 FFmpeg 수신기를 동시에 연결하고, `HealthTimeoutSec` 안에 각 채널에서 영상 1프레임을 디코딩해야 정상으로 판정합니다. `HealthFailureThreshold`회 연속 실패하면 FFmpeg PID가 남아 있어도 전체 AV 파이프라인을 강제로 정리하고 3초 안정화 후 다시 만듭니다. 재시작 횟수 제한은 없습니다.
|
||||||
|
|
||||||
등록된 작업은 `config.ini` 경로만 전달하므로 설정 파일을 수정한 뒤 작업을 다시 시작하면 변경값이 적용됩니다. 기존 작업이 등록돼 있다면 업데이트된 복구 설정을 적용하기 위해 `-InstallStartup`을 다시 실행해야 합니다.
|
등록된 작업은 `config.ini` 경로만 전달하므로 설정 파일을 수정한 뒤 작업을 다시 시작하면 변경값이 적용됩니다. 기존 작업이 등록돼 있다면 업데이트된 복구 설정을 적용하기 위해 `-InstallStartup`을 다시 실행해야 합니다.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
|||||||
@@ -120,6 +120,14 @@ HealthTarget=
|
|||||||
[RTSP]
|
[RTSP]
|
||||||
Port=8554
|
Port=8554
|
||||||
|
|
||||||
|
# 실행 중인 FFmpeg PID뿐 아니라 각 RTSP 채널에서 실제 영상 1프레임이
|
||||||
|
# 디코딩되는지를 검사합니다. 모든 채널 검사는 동시에 수행됩니다.
|
||||||
|
HealthCheckIntervalSec=10
|
||||||
|
HealthTimeoutSec=5
|
||||||
|
|
||||||
|
# 연속 실패가 이 횟수에 도달하면 전체 AV 파이프라인을 재시작합니다.
|
||||||
|
HealthFailureThreshold=2
|
||||||
|
|
||||||
[Dependencies]
|
[Dependencies]
|
||||||
# ffmpeg.exe 또는 mediamtx.exe가 없을 때 자동 다운로드: 1=사용, 0=오류 후 종료
|
# ffmpeg.exe 또는 mediamtx.exe가 없을 때 자동 다운로드: 1=사용, 0=오류 후 종료
|
||||||
AutoInstall=1
|
AutoInstall=1
|
||||||
|
|||||||
+189
-1
@@ -41,6 +41,15 @@ param(
|
|||||||
[ValidateRange(1, 65535)]
|
[ValidateRange(1, 65535)]
|
||||||
[int]$RtspPort = 8554,
|
[int]$RtspPort = 8554,
|
||||||
|
|
||||||
|
[ValidateRange(1, 3600)]
|
||||||
|
[int]$RtspHealthCheckIntervalSec = 10,
|
||||||
|
|
||||||
|
[ValidateRange(1, 60)]
|
||||||
|
[int]$RtspHealthTimeoutSec = 5,
|
||||||
|
|
||||||
|
[ValidateRange(1, 10)]
|
||||||
|
[int]$RtspHealthFailureThreshold = 2,
|
||||||
|
|
||||||
[ValidateSet(0, 1)]
|
[ValidateSet(0, 1)]
|
||||||
[int]$AutoInstallDependencies = 1,
|
[int]$AutoInstallDependencies = 1,
|
||||||
|
|
||||||
@@ -701,7 +710,12 @@ function Assert-IniSettings {
|
|||||||
Video = @("FrameRate", "Bitrate", "EncoderPriority")
|
Video = @("FrameRate", "Bitrate", "EncoderPriority")
|
||||||
Audio = @("Enabled", "MicrophoneEnabled", "Bitrate", "SignalThreshold")
|
Audio = @("Enabled", "MicrophoneEnabled", "Bitrate", "SignalThreshold")
|
||||||
Network = @("NicDescription", "HealthTarget")
|
Network = @("NicDescription", "HealthTarget")
|
||||||
RTSP = @("Port")
|
RTSP = @(
|
||||||
|
"Port",
|
||||||
|
"HealthCheckIntervalSec",
|
||||||
|
"HealthTimeoutSec",
|
||||||
|
"HealthFailureThreshold"
|
||||||
|
)
|
||||||
Dependencies = @(
|
Dependencies = @(
|
||||||
"AutoInstall",
|
"AutoInstall",
|
||||||
"FFmpegUrl",
|
"FFmpegUrl",
|
||||||
@@ -805,6 +819,18 @@ if ($ConfigWasLoaded) {
|
|||||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("RtspPort")) {
|
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("RtspPort")) {
|
||||||
$RtspPort = [int]$setting.Value
|
$RtspPort = [int]$setting.Value
|
||||||
}
|
}
|
||||||
|
$setting = Get-IniSetting $ini "RTSP" "HealthCheckIntervalSec"
|
||||||
|
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("RtspHealthCheckIntervalSec")) {
|
||||||
|
$RtspHealthCheckIntervalSec = [int]$setting.Value
|
||||||
|
}
|
||||||
|
$setting = Get-IniSetting $ini "RTSP" "HealthTimeoutSec"
|
||||||
|
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("RtspHealthTimeoutSec")) {
|
||||||
|
$RtspHealthTimeoutSec = [int]$setting.Value
|
||||||
|
}
|
||||||
|
$setting = Get-IniSetting $ini "RTSP" "HealthFailureThreshold"
|
||||||
|
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("RtspHealthFailureThreshold")) {
|
||||||
|
$RtspHealthFailureThreshold = [int]$setting.Value
|
||||||
|
}
|
||||||
|
|
||||||
$setting = Get-IniSetting $ini "Dependencies" "AutoInstall"
|
$setting = Get-IniSetting $ini "Dependencies" "AutoInstall"
|
||||||
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("AutoInstallDependencies")) {
|
if ($setting.Found -and -not $ExplicitParameters.ContainsKey("AutoInstallDependencies")) {
|
||||||
@@ -1004,6 +1030,9 @@ function Get-HiddenWorkerArgumentList {
|
|||||||
"-StableTimeSec", [string]$StableTimeSec,
|
"-StableTimeSec", [string]$StableTimeSec,
|
||||||
"-PollIntervalSec", [string]$PollIntervalSec,
|
"-PollIntervalSec", [string]$PollIntervalSec,
|
||||||
"-RtspPort", [string]$RtspPort,
|
"-RtspPort", [string]$RtspPort,
|
||||||
|
"-RtspHealthCheckIntervalSec", [string]$RtspHealthCheckIntervalSec,
|
||||||
|
"-RtspHealthTimeoutSec", [string]$RtspHealthTimeoutSec,
|
||||||
|
"-RtspHealthFailureThreshold", [string]$RtspHealthFailureThreshold,
|
||||||
"-AutoInstallDependencies", [string]$AutoInstallDependencies,
|
"-AutoInstallDependencies", [string]$AutoInstallDependencies,
|
||||||
"-FFmpegDownloadUrl", $FFmpegDownloadUrl,
|
"-FFmpegDownloadUrl", $FFmpegDownloadUrl,
|
||||||
"-FFmpegSha256Url", $FFmpegSha256Url,
|
"-FFmpegSha256Url", $FFmpegSha256Url,
|
||||||
@@ -1091,6 +1120,9 @@ if ($ShowConfiguration) {
|
|||||||
NicDescription = $NicDescription
|
NicDescription = $NicDescription
|
||||||
HealthTarget = $HealthTarget
|
HealthTarget = $HealthTarget
|
||||||
RtspPort = $RtspPort
|
RtspPort = $RtspPort
|
||||||
|
RtspHealthCheckIntervalSec = $RtspHealthCheckIntervalSec
|
||||||
|
RtspHealthTimeoutSec = $RtspHealthTimeoutSec
|
||||||
|
RtspHealthFailureThreshold = $RtspHealthFailureThreshold
|
||||||
AutoInstallDependencies = $AutoInstallDependencies
|
AutoInstallDependencies = $AutoInstallDependencies
|
||||||
FFmpegDownloadUrl = $FFmpegDownloadUrl
|
FFmpegDownloadUrl = $FFmpegDownloadUrl
|
||||||
FFmpegSha256Url = $FFmpegSha256Url
|
FFmpegSha256Url = $FFmpegSha256Url
|
||||||
@@ -2699,6 +2731,8 @@ $script:MediaMTXProcess = $null
|
|||||||
$script:OwnsMediaMTX = $false
|
$script:OwnsMediaMTX = $false
|
||||||
$script:StreamProcesses = @{}
|
$script:StreamProcesses = @{}
|
||||||
$script:AudioProcesses = @{}
|
$script:AudioProcesses = @{}
|
||||||
|
$script:LastRtspHealthCheck = [DateTime]::MinValue
|
||||||
|
$script:RtspHealthFailures = 0
|
||||||
|
|
||||||
function Test-ProcessRunning {
|
function Test-ProcessRunning {
|
||||||
param($Process)
|
param($Process)
|
||||||
@@ -2750,6 +2784,8 @@ function Stop-Streams {
|
|||||||
}
|
}
|
||||||
$script:StreamProcesses = @{}
|
$script:StreamProcesses = @{}
|
||||||
$script:AudioProcesses = @{}
|
$script:AudioProcesses = @{}
|
||||||
|
$script:LastRtspHealthCheck = [DateTime]::MinValue
|
||||||
|
$script:RtspHealthFailures = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function Get-RotationFilter {
|
function Get-RotationFilter {
|
||||||
@@ -2949,6 +2985,9 @@ function Start-Streams {
|
|||||||
}
|
}
|
||||||
throw "FFmpeg 또는 오디오 loopback worker가 실행 직후 종료되었습니다."
|
throw "FFmpeg 또는 오디오 loopback worker가 실행 직후 종료되었습니다."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$script:LastRtspHealthCheck = Get-Date
|
||||||
|
$script:RtspHealthFailures = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function Test-StreamsRunning {
|
function Test-StreamsRunning {
|
||||||
@@ -2963,6 +3002,92 @@ function Test-StreamsRunning {
|
|||||||
return $true
|
return $true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function New-RtspHealthProbeArguments {
|
||||||
|
param([int]$Channel)
|
||||||
|
|
||||||
|
return @(
|
||||||
|
"-hide_banner", "-loglevel", "error", "-nostdin",
|
||||||
|
"-rtsp_transport", "tcp",
|
||||||
|
"-rw_timeout", [string]($RtspHealthTimeoutSec * 1000000),
|
||||||
|
"-i", "$RTSPPublishBase/monitor$Channel",
|
||||||
|
"-map", "0:v:0", "-frames:v", "1",
|
||||||
|
"-an", "-sn", "-dn", "-f", "null", "NUL"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-RtspStreamFrames {
|
||||||
|
$probes = @()
|
||||||
|
$failures = New-Object System.Collections.Generic.List[string]
|
||||||
|
|
||||||
|
try {
|
||||||
|
for ($channel = 1; $channel -le $ChannelCount; $channel++) {
|
||||||
|
$errorLog = Join-Path $LogDir "rtsp-health-monitor$channel.log"
|
||||||
|
Remove-Item -LiteralPath $errorLog -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
$process = Start-Process -FilePath $FFmpegPath `
|
||||||
|
-ArgumentList (Join-ProcessArguments (New-RtspHealthProbeArguments $channel)) `
|
||||||
|
-WorkingDirectory $BaseDir -WindowStyle Hidden `
|
||||||
|
-RedirectStandardError $errorLog -PassThru
|
||||||
|
|
||||||
|
$probes += [PSCustomObject]@{
|
||||||
|
Channel = $channel
|
||||||
|
Process = $process
|
||||||
|
ErrorLog = $errorLog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start every receiver first and give the group one shared deadline.
|
||||||
|
# This keeps recovery time bounded even when every channel is stalled.
|
||||||
|
$deadline = [DateTime]::UtcNow.AddSeconds($RtspHealthTimeoutSec + 2)
|
||||||
|
foreach ($probe in $probes) {
|
||||||
|
$remainingMs = [Math]::Max(
|
||||||
|
1,
|
||||||
|
[int]($deadline - [DateTime]::UtcNow).TotalMilliseconds
|
||||||
|
)
|
||||||
|
$completed = $probe.Process.WaitForExit($remainingMs)
|
||||||
|
|
||||||
|
if (-not $completed) {
|
||||||
|
Stop-Process -Id $probe.Process.Id -Force -ErrorAction SilentlyContinue
|
||||||
|
$failures.Add(
|
||||||
|
"monitor$($probe.Channel): ${RtspHealthTimeoutSec}초 내 영상 프레임 없음"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$probe.Process.WaitForExit()
|
||||||
|
if ($probe.Process.ExitCode -ne 0) {
|
||||||
|
$detail = "exit=$($probe.Process.ExitCode)"
|
||||||
|
if (Test-Path -LiteralPath $probe.ErrorLog) {
|
||||||
|
$tail = @(
|
||||||
|
Get-Content -LiteralPath $probe.ErrorLog -Tail 3 `
|
||||||
|
-ErrorAction SilentlyContinue
|
||||||
|
)
|
||||||
|
if ($tail.Count -gt 0) { $detail = ($tail -join " | ") }
|
||||||
|
}
|
||||||
|
$failures.Add("monitor$($probe.Channel): $detail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$failures.Add($_.Exception.Message)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
foreach ($probe in $probes) {
|
||||||
|
try {
|
||||||
|
if (-not $probe.Process.HasExited) {
|
||||||
|
Stop-Process -Id $probe.Process.Id -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [PSCustomObject]@{
|
||||||
|
Healthy = ($failures.Count -eq 0)
|
||||||
|
Reason = ($failures -join "; ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function Get-StreamingReadinessReason {
|
function Get-StreamingReadinessReason {
|
||||||
param($Screens, $Network, $PlaybackEndpoint, $MicrophoneEndpoint)
|
param($Screens, $Network, $PlaybackEndpoint, $MicrophoneEndpoint)
|
||||||
|
|
||||||
@@ -3072,6 +3197,21 @@ function Invoke-SimulationTests {
|
|||||||
finally { [System.IO.File]::Delete($path) }
|
finally { [System.IO.File]::Delete($path) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Invoke-SimulationCase "RTSP 실제 프레임 검사 설정 파싱" {
|
||||||
|
$path = New-SimulationIni (
|
||||||
|
"[RTSP]`r`nPort=8554`r`nHealthCheckIntervalSec=10`r`n" +
|
||||||
|
"HealthTimeoutSec=5`r`nHealthFailureThreshold=2`r`n"
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
$parsed = Read-IniConfiguration $path
|
||||||
|
Assert-IniSettings $parsed
|
||||||
|
return ($parsed.RTSP.HealthCheckIntervalSec -eq "10" -and
|
||||||
|
$parsed.RTSP.HealthTimeoutSec -eq "5" -and
|
||||||
|
$parsed.RTSP.HealthFailureThreshold -eq "2")
|
||||||
|
}
|
||||||
|
finally { [System.IO.File]::Delete($path) }
|
||||||
|
}
|
||||||
|
|
||||||
Invoke-SimulationCase "자동 기록 갱신은 사용자 설정과 주석 보존" {
|
Invoke-SimulationCase "자동 기록 갱신은 사용자 설정과 주석 보존" {
|
||||||
$path = New-SimulationIni "# 사용자 주석`r`n[General]`r`nChannelCount=7`r`n"
|
$path = New-SimulationIni "# 사용자 주석`r`n[General]`r`nChannelCount=7`r`n"
|
||||||
try {
|
try {
|
||||||
@@ -3378,6 +3518,9 @@ function Invoke-SimulationTests {
|
|||||||
$arguments -match '-AudioEnabled\s+[01]' -and
|
$arguments -match '-AudioEnabled\s+[01]' -and
|
||||||
$arguments -match '-MicrophoneEnabled\s+[01]' -and
|
$arguments -match '-MicrophoneEnabled\s+[01]' -and
|
||||||
$arguments -match '-RtspPort\s+8554' -and
|
$arguments -match '-RtspPort\s+8554' -and
|
||||||
|
$arguments -match '-RtspHealthCheckIntervalSec\s+10' -and
|
||||||
|
$arguments -match '-RtspHealthTimeoutSec\s+5' -and
|
||||||
|
$arguments -match '-RtspHealthFailureThreshold\s+2' -and
|
||||||
$arguments -match '-WatchdogWorker')
|
$arguments -match '-WatchdogWorker')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3466,6 +3609,15 @@ function Invoke-SimulationTests {
|
|||||||
return (Test-StreamsRunning)
|
return (Test-StreamsRunning)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Invoke-SimulationCase "RTSP 상태 검사는 새 수신기로 실제 영상 1프레임 요구" {
|
||||||
|
$arguments = @(New-RtspHealthProbeArguments 2)
|
||||||
|
return ($arguments -contains '-rw_timeout' -and
|
||||||
|
$arguments -contains '-frames:v' -and
|
||||||
|
$arguments -contains '1' -and
|
||||||
|
$arguments -contains '0:v:0' -and
|
||||||
|
$arguments -contains "$RTSPPublishBase/monitor2")
|
||||||
|
}
|
||||||
|
|
||||||
Invoke-SimulationCase "비 HTTPS 의존성 URL 거부" {
|
Invoke-SimulationCase "비 HTTPS 의존성 URL 거부" {
|
||||||
try { Assert-HttpsDependencyUrl "http://example.com/file.zip" "test"; return $false }
|
try { Assert-HttpsDependencyUrl "http://example.com/file.zip" "test"; return $false }
|
||||||
catch { return $true }
|
catch { return $true }
|
||||||
@@ -3619,6 +3771,11 @@ try {
|
|||||||
if ($NicDescription) { Write-Log "고정 NIC: $NicDescription" }
|
if ($NicDescription) { Write-Log "고정 NIC: $NicDescription" }
|
||||||
else { Write-Log "NIC 선택: IPv4 기본 경로 자동 감지" }
|
else { Write-Log "NIC 선택: IPv4 기본 경로 자동 감지" }
|
||||||
if ($HealthTarget) { Write-Log "통신 확인 대상: $HealthTarget" }
|
if ($HealthTarget) { Write-Log "통신 확인 대상: $HealthTarget" }
|
||||||
|
Write-Log (
|
||||||
|
"RTSP 프레임 검사: ${RtspHealthCheckIntervalSec}초 주기 / " +
|
||||||
|
"${RtspHealthTimeoutSec}초 제한 / " +
|
||||||
|
"${RtspHealthFailureThreshold}회 연속 실패 시 재시작"
|
||||||
|
)
|
||||||
|
|
||||||
$lastSystemSignature = ""
|
$lastSystemSignature = ""
|
||||||
$healthySince = $null
|
$healthySince = $null
|
||||||
@@ -3733,6 +3890,37 @@ try {
|
|||||||
$healthySince = $null
|
$healthySince = $null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# A live FFmpeg PID does not prove that DDA and both RTSP outputs
|
||||||
|
# are progressing. A new local receiver must decode one frame from
|
||||||
|
# every channel within the configured deadline.
|
||||||
|
if ((Test-StreamsRunning) -and
|
||||||
|
((Get-Date) - $script:LastRtspHealthCheck).TotalSeconds -ge
|
||||||
|
$RtspHealthCheckIntervalSec) {
|
||||||
|
$script:LastRtspHealthCheck = Get-Date
|
||||||
|
$rtspHealth = Test-RtspStreamFrames
|
||||||
|
|
||||||
|
if ($rtspHealth.Healthy) {
|
||||||
|
if ($script:RtspHealthFailures -gt 0) {
|
||||||
|
Write-Log "RTSP 실제 프레임 수신 상태 복구"
|
||||||
|
}
|
||||||
|
$script:RtspHealthFailures = 0
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$script:RtspHealthFailures++
|
||||||
|
Write-Log (
|
||||||
|
"RTSP 실제 프레임 검사 실패 " +
|
||||||
|
"($($script:RtspHealthFailures)/$RtspHealthFailureThreshold): " +
|
||||||
|
$rtspHealth.Reason
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($script:RtspHealthFailures -ge $RtspHealthFailureThreshold) {
|
||||||
|
Write-Log "RTSP 송출 정지 감지 - 전체 AV 파이프라인 재시작"
|
||||||
|
Stop-Streams
|
||||||
|
$healthySince = $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (-not (Test-StreamsRunning)) {
|
if (-not (Test-StreamsRunning)) {
|
||||||
if (-not $healthySince) {
|
if (-not $healthySince) {
|
||||||
$healthySince = Get-Date
|
$healthySince = Get-Date
|
||||||
|
|||||||
Reference in new issue
Block a user