mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-15 09:36:42 +03:00
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.
This commit is contained in:
parent
6d5098a0c2
commit
7789260a83
@ -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: 10.0
|
||||
|
||||
# Optional: ffmpeg configuration
|
||||
# More information about presets at https://docs.frigate.video/configuration/ffmpeg_presets
|
||||
|
||||
@ -24,6 +24,12 @@ birdseye:
|
||||
restream: True
|
||||
```
|
||||
|
||||
**Tip:** To improve connection speed when using Birdseye via RTSP or WebRTC,
|
||||
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 +170,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}}
|
||||
```
|
||||
```
|
||||
@ -55,7 +55,11 @@ class BirdseyeConfig(FrigateBaseModel):
|
||||
layout: BirdseyeLayoutConfig = Field(
|
||||
default_factory=BirdseyeLayoutConfig, title="Birdseye Layout Config"
|
||||
)
|
||||
|
||||
idle_heartbeat_fps: float = Field(
|
||||
default=0.0,
|
||||
ge=0.0,
|
||||
title="Idle heartbeat FPS (0 disables)",
|
||||
)
|
||||
|
||||
# uses BaseModel because some global attributes are not available at the camera level
|
||||
class BirdseyeCameraConfig(BaseModel):
|
||||
|
||||
@ -11,6 +11,7 @@ import subprocess as sp
|
||||
import threading
|
||||
import traceback
|
||||
from typing import Any, Optional
|
||||
import time
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
@ -791,7 +792,16 @@ class Birdseye:
|
||||
self.frame_manager = SharedMemoryFrameManager()
|
||||
self.stop_event = stop_event
|
||||
self.requestor = InterProcessRequestor()
|
||||
self._heartbeat_thread = None
|
||||
|
||||
# --- Optional idle heartbeat (disabled by default) ---
|
||||
# If FRIGATE_BIRDSEYE_IDLE_FPS > 0, periodically re-send the last frame
|
||||
# when no frames have been output recently. This improves client attach times
|
||||
# without altering default behavior.
|
||||
self.idle_fps = float(self.config.birdseye.idle_heartbeat_fps or 0.0)
|
||||
self.idle_fps = max(0.0, self.idle_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(
|
||||
"birdseye",
|
||||
@ -801,6 +811,16 @@ class Birdseye:
|
||||
self.converter.start()
|
||||
self.broadcaster.start()
|
||||
|
||||
|
||||
# Start heartbeat loop only if enabled
|
||||
if self._idle_interval:
|
||||
self._heartbeat_thread = threading.Thread(
|
||||
target=self._idle_heartbeat_loop,
|
||||
name="birdseye_idle_heartbeat",
|
||||
daemon=True,
|
||||
)
|
||||
self._heartbeat_thread.start()
|
||||
|
||||
def __send_new_frame(self) -> None:
|
||||
frame_bytes = self.birdseye_manager.frame.tobytes()
|
||||
|
||||
@ -849,6 +869,27 @@ class Birdseye:
|
||||
coordinates = self.birdseye_manager.get_camera_coordinates()
|
||||
self.requestor.send_data(UPDATE_BIRDSEYE_LAYOUT, coordinates)
|
||||
|
||||
def _idle_heartbeat_loop(self) -> None:
|
||||
"""
|
||||
Periodically re-send the last composed frame when idle.
|
||||
Active only if FRIGATE_BIRDSEYE_IDLE_FPS > 0.
|
||||
"""
|
||||
# Small sleep granularity to check often without busy-spinning.
|
||||
min_sleep = 0.2
|
||||
while not self.stop_event.is_set():
|
||||
try:
|
||||
if self._idle_interval:
|
||||
now = datetime.datetime.now().timestamp()
|
||||
if (now - self.birdseye_manager.last_output_time) >= self._idle_interval:
|
||||
self.__send_new_frame()
|
||||
finally:
|
||||
# Sleep at the smaller of idle interval or a safe minimum
|
||||
sleep_for = self._idle_interval if self._idle_interval and self._idle_interval < min_sleep else min_sleep
|
||||
time.sleep(sleep_for)
|
||||
|
||||
def stop(self) -> None:
|
||||
self.converter.join()
|
||||
self.broadcaster.join()
|
||||
if self._heartbeat_thread and self._heartbeat_thread.is_alive():
|
||||
# the thread is daemon=True; join a moment just for cleanliness
|
||||
self._heartbeat_thread.join(timeout=0.2)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user