diff --git a/Dockerfile b/Dockerfile index 5be3464f5..a07a4d5a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -94,7 +94,9 @@ RUN --mount=type=bind,from=wheels,source=/wheels,target=/wheels \ fi \ # arch specific packages && if [ "${TARGETARCH}" = "amd64" ]; then \ - apt-get -qq install --no-install-recommends --no-install-suggests -y \ + echo 'deb http://deb.debian.org/debian testing main non-free' >> /etc/apt/sources.list.d/deb.list \ + && apt-get -qq update \ + && apt-get -qq install --no-install-recommends --no-install-suggests -y \ mesa-va-drivers libva-drm2 intel-media-va-driver-non-free i965-va-driver libmfx1; \ fi \ && if [ "${TARGETARCH}" = "arm64" ]; then \ diff --git a/docs/docs/configuration/hardware_acceleration.md b/docs/docs/configuration/hardware_acceleration.md index 54be32c1c..67df7ca60 100644 --- a/docs/docs/configuration/hardware_acceleration.md +++ b/docs/docs/configuration/hardware_acceleration.md @@ -58,6 +58,7 @@ services: devices: - driver: nvidia device_ids: ['0'] # this is only needed when using multiple GPUs + count: 1 # number of GPUs capabilities: [gpu] ``` diff --git a/docs/docs/configuration/restream.md b/docs/docs/configuration/restream.md index b11f0ca92..1f21e8fdc 100644 --- a/docs/docs/configuration/restream.md +++ b/docs/docs/configuration/restream.md @@ -5,8 +5,50 @@ title: Restream ### RTSP -Frigate can restream your video feed as an RTSP feed for other applications such as Home Assistant to utilize it at `rtsp://:8554/`. Port 8554 must be open. This allows you to use a video feed for detection in frigate and Home Assistant live view at the same time without having to make two separate connections to the camera. The video feed is copied from the original video feed directly to avoid re-encoding. This feed does not include any annotation by Frigate. +Frigate can restream your video feed as an RTSP feed for other applications such as Home Assistant to utilize it at `rtsp://:8554/`. Port 8554 must be open. [This allows you to use a video feed for detection in frigate and Home Assistant live view at the same time without having to make two separate connections to the camera](#reduce-connections-to-camera). The video feed is copied from the original video feed directly to avoid re-encoding. This feed does not include any annotation by Frigate. ### RTMP (Deprecated) In previous Frigate versions RTMP was used for re-streaming. RTMP has disadvantages however including being incompatible with H.265, high bitrates, and certain audio codecs. RTMP is deprecated and it is recommended to move to the new restream role. + +## Reduce Connections To Camera + +Some cameras only support one active connection or you may just want to have a single connection open to the camera. The RTSP restream allows this to be possible. + +### With Single Stream + +One connection is made to the camera. One for the restream, `detect` and `record` connect to the restream. + +```yaml +cameras: + test_cam: + ffmpeg: + inputs: + - path: rtsp://localhost:8554/test_cam # <--- the name here must match the name of the camera + roles: + - record + - detect + - path: rtsp://192.168.1.5:554/live0 # <--- 1 connection to camera stream + roles: + - restream +``` + +### With Sub Stream + +Two connections are made to the camera. One for the sub stream, one for the restream, `record` connects to the restream. + +```yaml +cameras: + test_cam: + ffmpeg: + inputs: + - path: rtsp://localhost:8554/test_cam # <--- the name here must match the name of the camera + roles: + - record + - path: rtsp://192.168.1.5:554/stream # <--- camera high res stream + roles: + - restream + - path: rtsp://192.168.1.5:554/substream # <--- camera sub stream + roles: + - detect +``` diff --git a/frigate/app.py b/frigate/app.py index 0f8acce66..f72093546 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -1,7 +1,7 @@ import logging import multiprocessing as mp from multiprocessing.queues import Queue -from multiprocessing.synchronize import Event +from multiprocessing.synchronize import Event as MpEvent import os import signal import sys @@ -38,10 +38,10 @@ logger = logging.getLogger(__name__) class FrigateApp: def __init__(self) -> None: - self.stop_event: Event = mp.Event() + self.stop_event: MpEvent = mp.Event() self.detection_queue: Queue = mp.Queue() self.detectors: dict[str, ObjectDetectProcess] = {} - self.detection_out_events: dict[str, Event] = {} + self.detection_out_events: dict[str, MpEvent] = {} self.detection_shms: list[mp.shared_memory.SharedMemory] = [] self.log_queue: Queue = mp.Queue() self.plus_api = PlusApi() @@ -355,6 +355,7 @@ class FrigateApp: print(e) self.log_process.terminate() sys.exit(1) + self.init_restream() self.start_detectors() self.start_video_output_processor() self.start_detected_frames_processor() @@ -362,7 +363,6 @@ class FrigateApp: self.start_camera_capture_processes() self.init_stats() self.init_web_server() - self.init_restream() self.start_mqtt_relay() self.start_event_processor() self.start_event_cleanup() diff --git a/frigate/events.py b/frigate/events.py index 2dfe40f3e..5f30f8633 100644 --- a/frigate/events.py +++ b/frigate/events.py @@ -11,43 +11,55 @@ from peewee import fn from frigate.config import EventsConfig, FrigateConfig, RecordConfig from frigate.const import CLIPS_DIR from frigate.models import Event +from frigate.types import CameraMetricsTypes + +from multiprocessing.queues import Queue +from multiprocessing.synchronize import Event as MpEvent +from typing import Dict logger = logging.getLogger(__name__) -def should_insert_db(prev_event, current_event): +def should_insert_db(prev_event: Event, current_event: Event) -> bool: """If current event has new clip or snapshot.""" return (not prev_event["has_clip"] and not prev_event["has_snapshot"]) and ( current_event["has_clip"] or current_event["has_snapshot"] ) -def should_update_db(prev_event, current_event): +def should_update_db(prev_event: Event, current_event: Event) -> bool: """If current_event has updated fields and (clip or snapshot).""" - return (current_event["has_clip"] or current_event["has_snapshot"]) and ( - prev_event["top_score"] != current_event["top_score"] - or prev_event["entered_zones"] != current_event["entered_zones"] - or prev_event["thumbnail"] != current_event["thumbnail"] - or prev_event["has_clip"] != current_event["has_clip"] - or prev_event["has_snapshot"] != current_event["has_snapshot"] - ) + if current_event["has_clip"] or current_event["has_snapshot"]: + if ( + prev_event["top_score"] != current_event["top_score"] + or prev_event["entered_zones"] != current_event["entered_zones"] + or prev_event["thumbnail"] != current_event["thumbnail"] + or prev_event["has_clip"] != current_event["has_clip"] + or prev_event["has_snapshot"] != current_event["has_snapshot"] + ): + return True + return False class EventProcessor(threading.Thread): def __init__( - self, config, camera_processes, event_queue, event_processed_queue, stop_event + self, + config: FrigateConfig, + camera_processes: dict[str, CameraMetricsTypes], + event_queue: Queue, + event_processed_queue: Queue, + stop_event: MpEvent, ): threading.Thread.__init__(self) self.name = "event_processor" self.config = config self.camera_processes = camera_processes - self.cached_clips = {} self.event_queue = event_queue self.event_processed_queue = event_processed_queue - self.events_in_process = {} + self.events_in_process: Dict[str, Event] = {} self.stop_event = stop_event - def run(self): + def run(self) -> None: # set an end_time on events without an end_time on startup Event.update(end_time=Event.start_time + 30).where( Event.end_time == None @@ -147,14 +159,15 @@ class EventProcessor(threading.Thread): class EventCleanup(threading.Thread): - def __init__(self, config: FrigateConfig, stop_event): + def __init__(self, config: FrigateConfig, stop_event: MpEvent): threading.Thread.__init__(self) self.name = "event_cleanup" self.config = config self.stop_event = stop_event self.camera_keys = list(self.config.cameras.keys()) - def expire(self, media_type): + def expire(self, media_type: str) -> None: + # TODO: Refactor media_type to enum ## Expire events from unlisted cameras based on the global config if media_type == "clips": retain_config = self.config.record.events.retain @@ -253,7 +266,7 @@ class EventCleanup(threading.Thread): ) update_query.execute() - def purge_duplicates(self): + def purge_duplicates(self) -> None: duplicate_query = """with grouped_events as ( select id, label, @@ -287,7 +300,7 @@ class EventCleanup(threading.Thread): .execute() ) - def run(self): + def run(self) -> None: # only expire events every 5 minutes while not self.stop_event.wait(300): self.expire("clips") diff --git a/frigate/mypy.ini b/frigate/mypy.ini index c789241d0..4769ab96a 100644 --- a/frigate/mypy.ini +++ b/frigate/mypy.ini @@ -34,6 +34,9 @@ disallow_untyped_calls = false [mypy-frigate.const] ignore_errors = false +[mypy-frigate.events] +ignore_errors = false + [mypy-frigate.log] ignore_errors = false diff --git a/frigate/stats.py b/frigate/stats.py index d4899bb6d..62c8eeec6 100644 --- a/frigate/stats.py +++ b/frigate/stats.py @@ -8,7 +8,7 @@ import os import requests from typing import Optional, Any from paho.mqtt.client import Client -from multiprocessing.synchronize import Event +from multiprocessing.synchronize import Event as MpEvent from frigate.config import FrigateConfig from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR @@ -148,7 +148,7 @@ class StatsEmitter(threading.Thread): stats_tracking: StatsTrackingTypes, mqtt_client: Client, topic_prefix: str, - stop_event: Event, + stop_event: MpEvent, ): threading.Thread.__init__(self) self.name = "frigate_stats_emitter" diff --git a/frigate/watchdog.py b/frigate/watchdog.py index 4316dc316..96ba2e371 100644 --- a/frigate/watchdog.py +++ b/frigate/watchdog.py @@ -7,13 +7,13 @@ import signal from frigate.object_detection import ObjectDetectProcess from frigate.util import restart_frigate -from multiprocessing.synchronize import Event +from multiprocessing.synchronize import Event as MpEvent logger = logging.getLogger(__name__) class FrigateWatchdog(threading.Thread): - def __init__(self, detectors: dict[str, ObjectDetectProcess], stop_event: Event): + def __init__(self, detectors: dict[str, ObjectDetectProcess], stop_event: MpEvent): threading.Thread.__init__(self) self.name = "frigate_watchdog" self.detectors = detectors diff --git a/requirements-dev.txt b/requirements-dev.txt index 5cba885d9..dbc8f87f4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,2 @@ -pylint == 2.13.* -black == 22.3.* +pylint == 2.15.* +black == 22.10.* diff --git a/requirements-wheels.txt b/requirements-wheels.txt index 2dd2356cc..7df0be770 100644 --- a/requirements-wheels.txt +++ b/requirements-wheels.txt @@ -10,10 +10,10 @@ peewee == 3.14.* peewee_migrate == 1.4.* psutil == 5.9.* pydantic == 1.9.* -PyYAML == 6.0.* +PyYAML == 6.0 types-PyYAML == 6.0.* requests == 2.27.* -types-requests == 2.27.* +types-requests == 2.28.* scipy == 1.8.* setproctitle == 1.2.* ws4py == 0.5.* diff --git a/web/src/routes/Debug.jsx b/web/src/routes/Debug.jsx index 3c8c8a8ff..f96b40b03 100644 --- a/web/src/routes/Debug.jsx +++ b/web/src/routes/Debug.jsx @@ -159,14 +159,14 @@ export default function Debug() { {cameras[camera]['detection_fps']} ({cameras[camera]['skipped_fps']} skipped) {cpu_usages[cameras[camera]['pid']]['cpu']}% - {cpu_usages[cameras[camera]['pid']]['cpu']}% + {cpu_usages[cameras[camera]['pid']]['mem']}% ffmpeg {cameras[camera]['ffmpeg_pid']} {cameras[camera]['camera_fps']} {cpu_usages[cameras[camera]['ffmpeg_pid']]['cpu']}% - {cpu_usages[cameras[camera]['ffmpeg_pid']]['cpu']}% + {cpu_usages[cameras[camera]['ffmpeg_pid']]['mem']}%