From 4c689dde8ed6110ec1216c2bd3f9213dfd1cdfc9 Mon Sep 17 00:00:00 2001 From: Francesco Durighetto Date: Sun, 19 Oct 2025 13:20:36 +0200 Subject: [PATCH] Add optional idle heartbeat for Birdseye (#20453) * Add optional idle heartbeat for Birdseye (periodic frame emission when idle) birdseye: add optional idle heartbeat and FFmpeg tuning envs (default off) This adds an optional configuration field `birdseye.idle_heartbeat_fps` to enable a lightweight idle heartbeat mechanism in Birdseye. When set to a value greater than 0, Birdseye periodically re-sends the last composed frame during idle periods (no motion or active updates). This helps downstream consumers such as go2rtc, Alexa, or Scrypted to attach faster and maintain a low-latency RTSP stream when the system is idle. Key details: - Config-based (`birdseye.idle_heartbeat_fps`), default `0` (disabled). - Uses existing Birdseye rendering pipeline; minimal performance impact. - Does not alter behavior when unset. Documentation: added tip section in docs/configuration/restream.md. * Update docs/docs/configuration/restream.md Co-authored-by: Nicolas Mowen * Update docs/docs/configuration/reference.md Co-authored-by: Nicolas Mowen * Refactors Birdseye idle frame broadcasting Simplifies the idle frame broadcasting logic by removing the dedicated thread. The idle frame is now resent directly within the main loop, improving efficiency and reducing complexity. Also, limits the idle heartbeat FPS to a maximum of 10 since the framebuffer is limited to 10 anyway * ruff fix --------- Co-authored-by: Nicolas Mowen Co-authored-by: Francesco Durighetto Co-authored-by: duri --- docs/docs/configuration/reference.md | 2 ++ docs/docs/configuration/restream.md | 7 ++++++- frigate/config/camera/birdseye.py | 6 ++++++ frigate/output/birdseye.py | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index c12984d18..3d963a5bd 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -240,6 +240,8 @@ birdseye: scaling_factor: 2.0 # Optional: Maximum number of cameras to show at one time, showing the most recent (default: show all cameras) max_cameras: 1 + # Optional: Frames-per-second to re-send the last composed Birdseye frame when idle (no motion or active updates). (default: shown below) + idle_heartbeat_fps: 0.0 # Optional: ffmpeg configuration # More information about presets at https://docs.frigate.video/configuration/ffmpeg_presets diff --git a/docs/docs/configuration/restream.md b/docs/docs/configuration/restream.md index 43c421dd3..0ab7a170c 100644 --- a/docs/docs/configuration/restream.md +++ b/docs/docs/configuration/restream.md @@ -24,6 +24,11 @@ birdseye: restream: True ``` +:::tip + +To improve connection speed when using Birdseye via restream you can enable a small idle heartbeat by setting `birdseye.idle_heartbeat_fps` to a low value (e.g. `1–2`). This makes Frigate periodically push the last frame even when no motion is detected, reducing initial connection latency. + +::: ### Securing Restream With Authentication The go2rtc restream can be secured with RTSP based username / password authentication. Ex: @@ -164,4 +169,4 @@ NOTE: The output will need to be passed with two curly braces `{{output}}` go2rtc: streams: stream1: exec:ffmpeg -hide_banner -re -stream_loop -1 -i /media/BigBuckBunny.mp4 -c copy -rtsp_transport tcp -f rtsp {{output}} -``` +``` \ No newline at end of file diff --git a/frigate/config/camera/birdseye.py b/frigate/config/camera/birdseye.py index b7e8a7117..1e6f0f335 100644 --- a/frigate/config/camera/birdseye.py +++ b/frigate/config/camera/birdseye.py @@ -55,6 +55,12 @@ class BirdseyeConfig(FrigateBaseModel): layout: BirdseyeLayoutConfig = Field( default_factory=BirdseyeLayoutConfig, title="Birdseye Layout Config" ) + idle_heartbeat_fps: float = Field( + default=0.0, + ge=0.0, + le=10.0, + title="Idle heartbeat FPS (0 disables, max 10)", + ) # uses BaseModel because some global attributes are not available at the camera level diff --git a/frigate/output/birdseye.py b/frigate/output/birdseye.py index 0939b5ce4..eb23c2573 100644 --- a/frigate/output/birdseye.py +++ b/frigate/output/birdseye.py @@ -9,6 +9,7 @@ import os import queue import subprocess as sp import threading +import time import traceback from typing import Any, Optional @@ -791,6 +792,10 @@ class Birdseye: self.frame_manager = SharedMemoryFrameManager() self.stop_event = stop_event self.requestor = InterProcessRequestor() + self.idle_fps: float = self.config.birdseye.idle_heartbeat_fps + self._idle_interval: Optional[float] = ( + (1.0 / self.idle_fps) if self.idle_fps > 0 else None + ) if config.birdseye.restream: self.birdseye_buffer = self.frame_manager.create( @@ -848,6 +853,15 @@ class Birdseye: if frame_layout_changed: coordinates = self.birdseye_manager.get_camera_coordinates() self.requestor.send_data(UPDATE_BIRDSEYE_LAYOUT, coordinates) + if self._idle_interval: + now = time.monotonic() + is_idle = len(self.birdseye_manager.camera_layout) == 0 + if ( + is_idle + and (now - self.birdseye_manager.last_output_time) + >= self._idle_interval + ): + self.__send_new_frame() def stop(self) -> None: self.converter.join()