Merge branch 'dev' into zooming

This commit is contained in:
Josh Hawkins 2023-09-01 08:12:37 -05:00
commit 2b52028d15
22 changed files with 122 additions and 32 deletions

View File

@ -71,22 +71,22 @@ ENV CCACHE_MAXSIZE 2G
# Build libUSB without udev. Needed for Openvino NCS2 support
WORKDIR /opt
RUN apt-get update && apt-get install -y unzip build-essential automake libtool ccache
RUN --mount=type=cache,target=/root/.ccache wget -q https://github.com/libusb/libusb/archive/v1.0.25.zip -O v1.0.25.zip && \
unzip v1.0.25.zip && cd libusb-1.0.25 && \
RUN apt-get update && apt-get install -y unzip build-essential automake libtool ccache pkg-config
RUN --mount=type=cache,target=/root/.ccache wget -q https://github.com/libusb/libusb/archive/v1.0.26.zip -O v1.0.26.zip && \
unzip v1.0.26.zip && cd libusb-1.0.26 && \
./bootstrap.sh && \
./configure CC='ccache gcc' CCX='ccache g++' --disable-udev --enable-shared && \
make -j $(nproc --all)
RUN apt-get update && \
apt-get install -y --no-install-recommends libusb-1.0-0-dev && \
rm -rf /var/lib/apt/lists/*
WORKDIR /opt/libusb-1.0.25/libusb
WORKDIR /opt/libusb-1.0.26/libusb
RUN /bin/mkdir -p '/usr/local/lib' && \
/bin/bash ../libtool --mode=install /usr/bin/install -c libusb-1.0.la '/usr/local/lib' && \
/bin/mkdir -p '/usr/local/include/libusb-1.0' && \
/usr/bin/install -c -m 644 libusb.h '/usr/local/include/libusb-1.0' && \
/bin/mkdir -p '/usr/local/lib/pkgconfig' && \
cd /opt/libusb-1.0.25/ && \
cd /opt/libusb-1.0.26/ && \
/usr/bin/install -c -m 644 libusb-1.0.pc '/usr/local/lib/pkgconfig' && \
ldconfig

View File

@ -2,7 +2,7 @@
set -euxo pipefail
NGINX_VERSION="1.25.1"
NGINX_VERSION="1.25.2"
VOD_MODULE_VERSION="1.31"
SECURE_TOKEN_MODULE_VERSION="1.5"
RTMP_MODULE_VERSION="1.2.2"

View File

@ -2,7 +2,7 @@
set -euxo pipefail
s6_version="3.1.4.1"
s6_version="3.1.5.0"
if [[ "${TARGETARCH}" == "amd64" ]]; then
s6_arch="x86_64"

View File

@ -39,6 +39,10 @@ if go2rtc_config.get("api") is None:
elif go2rtc_config["api"].get("origin") is None:
go2rtc_config["api"]["origin"] = "*"
# Need to set default location for HA config
if go2rtc_config.get("hass") is None:
go2rtc_config["hass"] = {"config": "/config"}
# we want to ensure that logs are easy to read
if go2rtc_config.get("log") is None:
go2rtc_config["log"] = {"format": "text"}

View File

@ -50,7 +50,7 @@ cameras:
### Configuring Audio Events
The included audio model has over 500 different types of audio that can be detected, many of which are not practical. By default `bark`, `speech`, `yell`, and `scream` are enabled but these can be customized.
The included audio model has over [500 different types](https://github.com/blakeblackshear/frigate/blob/dev/audio-labelmap.txt) of audio that can be detected, many of which are not practical. By default `bark`, `speech`, `yell`, and `scream` are enabled but these can be customized.
```yaml
audio:

View File

@ -11,7 +11,7 @@ Once Frigate determines that an object is not a false positive and has entered o
Upon loss of tracking, Frigate will scan the region of the lost object for `timeout` seconds. If an object of the same type is found in that region, Frigate will autotrack that new object.
When tracking has ended, Frigate will return to the camera preset specified by the `return_preset` configuration entry.
When tracking has ended, Frigate will return to the camera firmware's PTZ preset specified by the `return_preset` configuration entry.
## Checking ONVIF camera support
@ -23,7 +23,7 @@ Alternatively, you can download and run [this simple Python script](https://gist
## Configuration
First, set up a PTZ preset in your camera's firmware and give it a name.
First, set up a PTZ preset in your camera's firmware and give it a name. If you're unsure how to do this, consult the documentation for your camera manufacturer's firmware. Some tutorials for common brands: [Amcrest](https://www.youtube.com/watch?v=lJlE9-krmrM), [Reolink](https://www.youtube.com/watch?v=VAnxHUY5i5w), [Dahua](https://www.youtube.com/watch?v=7sNbc5U-k54).
Edit your Frigate configuration file and enter the ONVIF parameters for your camera. Specify the object types to track, a required zone the object must enter to begin autotracking, and the camera preset name you configured in your camera's firmware to return to when tracking has ended. Optionally, specify a delay in seconds before Frigate returns the camera to the preset.
@ -56,7 +56,7 @@ cameras:
# Required: Begin automatically tracking an object when it enters any of the listed zones.
required_zones:
- zone_name
# Required: Name of ONVIF camera preset to return to when tracking is over. (default: shown below)
# Required: Name of ONVIF preset in camera's firmware to return to when tracking is over. (default: shown below)
return_preset: home
# Optional: Seconds to delay before returning to preset. (default: shown below)
timeout: 10

View File

@ -579,7 +579,7 @@ cameras:
# Required: Begin automatically tracking an object when it enters any of the listed zones.
required_zones:
- zone_name
# Required: Name of ONVIF camera preset to return to when tracking is over.
# Required: Name of ONVIF preset in camera's firmware to return to when tracking is over. (default: shown below)
return_preset: preset_name
# Optional: Seconds to delay before returning to preset. (default: shown below)
timeout: 10

View File

@ -25,8 +25,8 @@ Frigate uses the following locations for read/write operations in the container.
- `/media/frigate/clips`: Used for snapshot storage. In the future, it will likely be renamed from `clips` to `snapshots`. The file structure here cannot be modified and isn't intended to be browsed or managed manually.
- `/media/frigate/recordings`: Internal system storage for recording segments. The file structure here cannot be modified and isn't intended to be browsed or managed manually.
- `/media/frigate/exports`: Storage for clips and timelapses that have been exported via the WebUI or API.
- `/tmp/cache`: Cache location for recording segments. Initial recordings are written here before being checked and converted to mp4 and moved to the recordings folder.
- `/dev/shm`: It is not recommended to modify this directory or map it with docker. This is the location for raw decoded frames in shared memory and it's size is impacted by the `shm-size` calculations below.
- `/tmp/cache`: Cache location for recording segments. Initial recordings are written here before being checked and converted to mp4 and moved to the recordings folder. Segments generated via the `clip.mp4` endpoints are also concatenated and processed here. It is recommended to use a [`tmpfs`](https://docs.docker.com/storage/tmpfs/) mount for this.
- `/dev/shm`: Internal cache for raw decoded frames in shared memory. It is not recommended to modify this directory or map it with docker. The minimum size is impacted by the `shm-size` calculations below.
#### Common docker compose storage configurations
@ -51,7 +51,7 @@ services:
Frigate utilizes shared memory to store frames during processing. The default `shm-size` provided by Docker is **64MB**.
The default shm size of **64MB** is fine for setups with **2 cameras** detecting at **720p**. If Frigate is exiting with "Bus error" messages, it is likely because you have too many high resolution cameras and you need to specify a higher shm size.
The default shm size of **64MB** is fine for setups with **2 cameras** detecting at **720p**. If Frigate is exiting with "Bus error" messages, it is likely because you have too many high resolution cameras and you need to specify a higher shm size, using [`--shm-size`](https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources) (or [`service.shm_size`](https://docs.docker.com/compose/compose-file/compose-file-v2/#shm_size) in docker-compose).
The Frigate container also stores logs in shm, which can take up to **30MB**, so make sure to take this into account in your math as well.

View File

@ -0,0 +1,38 @@
---
id: recordings
title: Troubleshooting Recordings
---
## `WARNING : Unable to keep up with recording segments in cache for {camera}. Keeping the 5 most recent segments out of 6 and discarding the rest...`
This error can be caused by a number of different issues. The first step in troubleshooting is to enable debug logging for recording, this will enable logging showing how long it takes for recordings to be moved from RAM cache to the disk.
```yaml
logger:
logs:
frigate.record.maintainer: debug
```
This will include logs like:
```
DEBUG : Copied /media/frigate/recordings/{segment_path} in 0.2 seconds.
```
It is important to let this run until the errors begin to happen, to confirm that there is not a slow down in the disk at the time of the error.
### Copy Times > 1 second
If the storage is too slow to keep up with the recordings then the maintainer will fall behind and purge the oldest recordings to ensure the cache does not fill up causing a crash. In this case it is important to diagnose why the copy times are slow.
#### Check Storage Type
Mounting a network share is a popular option for storing Recordings, but this can lead to reduced copy times and cause problems. Some users have found that using `NFS` instead of `SMB` considerably decreased the copy times and fixed the issue. It is also important to ensure that the network connection between the device running Frigate and the network share is stable and fast.
#### Check mount options
Some users found that mounting a drive via `fstab` with the `sync` option caused dramatically reduce performance and led to this issue. Using `async` instead greatly reduced copy times.
### Copy Times < 1 second
If the storage is working quickly then this error may be caused by CPU load on the machine being too high for Frigate to have the resources to keep up. Try temporarily shutting down other services to see if the issue improves.

View File

@ -58,6 +58,7 @@ module.exports = {
],
Troubleshooting: [
"troubleshooting/faqs",
"troubleshooting/recordings",
],
Development: [
"development/contributing",

View File

@ -52,9 +52,6 @@ class Dispatcher:
self.ptz_metrics = ptz_metrics
self.comms = communicators
for comm in self.comms:
comm.subscribe(self._receive)
self._camera_settings_handlers: dict[str, Callable] = {
"audio": self._on_audio_command,
"detect": self._on_detect_command,
@ -67,6 +64,9 @@ class Dispatcher:
"snapshots": self._on_snapshots_command,
}
for comm in self.comms:
comm.subscribe(self._receive)
def _receive(self, topic: str, payload: str) -> None:
"""Handle receiving of payload from communicators."""
if topic.endswith("set"):

View File

@ -444,6 +444,7 @@ class AudioConfig(FrigateBaseModel):
enabled_in_config: Optional[bool] = Field(
title="Keep track of original state of audio detection."
)
num_threads: int = Field(default=2, title="Number of detection threads", ge=1)
class BirdseyeModeEnum(str, Enum):

View File

@ -89,12 +89,13 @@ def listen_to_audio(
class AudioTfl:
def __init__(self, stop_event: mp.Event):
def __init__(self, stop_event: mp.Event, num_threads=2):
self.stop_event = stop_event
self.labels = load_labels("/audio-labelmap.txt")
self.num_threads = num_threads
self.labels = load_labels("/audio-labelmap.txt", prefill=521)
self.interpreter = Interpreter(
model_path="/cpu_audio_model.tflite",
num_threads=2,
num_threads=self.num_threads,
)
self.interpreter.allocate_tensors()
@ -117,7 +118,7 @@ class AudioTfl:
count = len(scores)
for i in range(count):
if scores[i] < 0.4 or i == 20:
if scores[i] < AUDIO_MIN_CONFIDENCE or i == 20:
break
detections[i] = [
class_ids[i],
@ -164,7 +165,7 @@ class AudioEventMaintainer(threading.Thread):
self.inter_process_communicator = inter_process_communicator
self.detections: dict[dict[str, any]] = feature_metrics
self.stop_event = stop_event
self.detector = AudioTfl(stop_event)
self.detector = AudioTfl(stop_event, self.config.audio.num_threads)
self.shape = (int(round(AUDIO_DURATION * AUDIO_SAMPLE_RATE)),)
self.chunk_size = int(round(AUDIO_DURATION * AUDIO_SAMPLE_RATE * 2))
self.logger = logging.getLogger(f"audio.{self.config.name}")

View File

@ -168,6 +168,7 @@ class EventCleanup(threading.Thread):
camera,
has_snapshot,
has_clip,
end_time,
row_number() over (
partition by label, camera, round(start_time/5,0)*5
order by end_time-start_time desc
@ -176,7 +177,7 @@ class EventCleanup(threading.Thread):
)
select distinct id, camera, has_snapshot, has_clip from grouped_events
where copy_number > 1;"""
where copy_number > 1 and end_time not null;"""
duplicate_events = Event.raw(duplicate_query)
for event in duplicate_events:

View File

@ -29,6 +29,7 @@ from frigate.util.image import (
calculate_region,
draw_box_with_label,
draw_timestamp,
is_label_printable,
)
logger = logging.getLogger(__name__)
@ -509,13 +510,21 @@ class CameraState:
# draw the bounding boxes on the frame
box = obj["box"]
text = (
obj["label"]
if (
not obj.get("sub_label")
or not is_label_printable(obj["sub_label"][0])
)
else obj["sub_label"][0]
)
draw_box_with_label(
frame_copy,
box[0],
box[1],
box[2],
box[3],
obj["label"],
text,
f"{obj['score']:.0%} {int(obj['area'])}",
thickness=thickness,
color=color,

View File

@ -210,7 +210,7 @@ class BroadcastThread(threading.Thread):
ws.send(buf, binary=True)
except ValueError:
pass
except ConnectionResetError as e:
except (BrokenPipeError, ConnectionResetError) as e:
logger.debug(f"Websocket unexpectedly closed {e}")
elif self.converter.process.poll() is not None:
break

View File

@ -119,10 +119,16 @@ class OnvifController:
move_request.Translation.PanTilt.space = ptz_config["Spaces"][
"RelativePanTiltTranslationSpace"
][fov_space_id]["URI"]
try:
if zoom_space_id is not None:
move_request.Translation.Zoom.space = ptz_config["Spaces"][
"RelativeZoomTranslationSpace"
][0]["URI"]
except Exception:
# camera does not support relative zoom
pass
if move_request.Speed is None:
move_request.Speed = ptz.GetStatus({"ProfileToken": profile.token}).Position
self.cams[camera_name]["relative_move_request"] = move_request

View File

@ -134,7 +134,7 @@ def get_ffmpeg_arg_list(arg: Any) -> list:
return arg if isinstance(arg, list) else shlex.split(arg)
def load_labels(path, encoding="utf-8"):
def load_labels(path, encoding="utf-8", prefill=91):
"""Loads labels from file (with or without index numbers).
Args:
path: path to label file.
@ -143,7 +143,7 @@ def load_labels(path, encoding="utf-8"):
Dictionary mapping indices to labels.
"""
with open(path, "r", encoding=encoding) as f:
labels = {index: "unknown" for index in range(91)}
labels = {index: "unknown" for index in range(prefill)}
lines = f.readlines()
if not lines:
return {}

View File

@ -4,6 +4,7 @@ import datetime
import logging
from abc import ABC, abstractmethod
from multiprocessing import shared_memory
from string import printable
from typing import AnyStr, Optional
import cv2
@ -154,6 +155,11 @@ def draw_box_with_label(
)
def is_label_printable(label) -> bool:
"""Check if label is printable."""
return not bool(set(label) - set(printable))
def calculate_region(frame_shape, xmin, ymin, xmax, ymax, model_size, multiplier=2):
# size is the longest edge and divisible by 4
size = int((max(xmax - xmin, ymax - ymin) * multiplier) // 4 * 4)

View File

@ -5,6 +5,8 @@ import { ArrowDropdown } from '../icons/ArrowDropdown';
import Heading from './Heading';
import Button from './Button';
import CameraIcon from '../icons/Camera';
import SpeakerIcon from '../icons/Speaker';
import useSWR from 'swr';
export default function MultiSelect({ className, title, options, selection, onToggle, onShowAll, onSelectSingle }) {
const popupRef = useRef(null);
@ -18,7 +20,7 @@ export default function MultiSelect({ className, title, options, selection, onTo
};
const menuHeight = Math.round(window.innerHeight * 0.55);
const { data: config } = useSWR('config');
return (
<div className={`${className} p-2`} ref={popupRef}>
<div className="flex justify-between min-w-[120px]" onClick={() => setState({ showMenu: true })}>
@ -59,7 +61,8 @@ export default function MultiSelect({ className, title, options, selection, onTo
className="max-h-[35px] mx-2"
onClick={() => onSelectSingle(item)}
>
<CameraIcon />
{ (title === "Labels" && config.audio.listen.includes(item)) ? ( <SpeakerIcon /> ) : ( <CameraIcon /> ) }
</Button>
</div>
</div>

View File

@ -1,7 +1,7 @@
import { h } from 'preact';
import { memo } from 'preact/compat';
export function Snapshot({ className = 'h-6 w-6', stroke = 'currentColor', onClick = () => {} }) {
export function Audio({ className = 'h-6 w-6', stroke = 'currentColor', onClick = () => {} }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
@ -33,4 +33,4 @@ export function Snapshot({ className = 'h-6 w-6', stroke = 'currentColor', onCli
);
}
export default memo(Snapshot);
export default memo(Audio);

20
web/src/icons/Speaker.jsx Normal file
View File

@ -0,0 +1,20 @@
import { h } from 'preact';
import { memo } from 'preact/compat';
export function Speaker({
className = 'h-7 w-7',
stroke = 'currentColor',
onClick = () => {},
}) {
return (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32"
stroke={stroke}
onClick={onClick}
className={className}>
<path stroke-linecap="round" stroke-linejoin="round" strokeWidth="2" d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z"/>
</svg>
);
}
export default memo(Speaker);