mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-11 17:47:37 +03:00
Compare commits
2 Commits
f9e06bb7b7
...
7fb8d9b050
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fb8d9b050 | ||
|
|
b8bc98a423 |
@ -139,7 +139,11 @@ record:
|
|||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
|
|
||||||
When using `hwaccel_args` globally hardware encoding is used for time lapse generation. The encoder determines its own behavior so the resulting file size may be undesirably large.
|
When using `hwaccel_args`, hardware encoding is used for timelapse generation. This setting can be overridden for a specific camera (e.g., when camera resolution exceeds hardware encoder limits); set `cameras.<camera>.record.export.hwaccel_args` with the appropriate settings. Using an unrecognized value or empty string will fall back to software encoding (libx264).
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
The encoder determines its own behavior so the resulting file size may be undesirably large.
|
||||||
To reduce the output file size the ffmpeg parameter `-qp n` can be utilized (where `n` stands for the value of the quantisation parameter). The value can be adjusted to get an acceptable tradeoff between quality and file size for the given scenario.
|
To reduce the output file size the ffmpeg parameter `-qp n` can be utilized (where `n` stands for the value of the quantisation parameter). The value can be adjusted to get an acceptable tradeoff between quality and file size for the given scenario.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|||||||
@ -534,6 +534,8 @@ record:
|
|||||||
# The -r (framerate) dictates how smooth the output video is.
|
# The -r (framerate) dictates how smooth the output video is.
|
||||||
# So the args would be -vf setpts=0.02*PTS -r 30 in that case.
|
# So the args would be -vf setpts=0.02*PTS -r 30 in that case.
|
||||||
timelapse_args: "-vf setpts=0.04*PTS -r 30"
|
timelapse_args: "-vf setpts=0.04*PTS -r 30"
|
||||||
|
# Optional: Global hardware acceleration settings for timelapse exports. (default: inherit)
|
||||||
|
hwaccel_args: auto
|
||||||
# Optional: Recording Preview Settings
|
# Optional: Recording Preview Settings
|
||||||
preview:
|
preview:
|
||||||
# Optional: Quality of recording preview (default: shown below).
|
# Optional: Quality of recording preview (default: shown below).
|
||||||
@ -835,6 +837,11 @@ cameras:
|
|||||||
# Optional: camera specific output args (default: inherit)
|
# Optional: camera specific output args (default: inherit)
|
||||||
# output_args:
|
# output_args:
|
||||||
|
|
||||||
|
# Optional: camera specific hwaccel args for timelapse export (default: inherit)
|
||||||
|
# record:
|
||||||
|
# export:
|
||||||
|
# hwaccel_args:
|
||||||
|
|
||||||
# Optional: timeout for highest scoring image before allowing it
|
# Optional: timeout for highest scoring image before allowing it
|
||||||
# to be replaced by a newer image. (default: shown below)
|
# to be replaced by a newer image. (default: shown below)
|
||||||
best_image_timeout: 60
|
best_image_timeout: 60
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
@ -70,6 +70,9 @@ class RecordExportConfig(FrigateBaseModel):
|
|||||||
timelapse_args: str = Field(
|
timelapse_args: str = Field(
|
||||||
default=DEFAULT_TIME_LAPSE_FFMPEG_ARGS, title="Timelapse Args"
|
default=DEFAULT_TIME_LAPSE_FFMPEG_ARGS, title="Timelapse Args"
|
||||||
)
|
)
|
||||||
|
hwaccel_args: Union[str, list[str]] = Field(
|
||||||
|
default="auto", title="Export-specific FFmpeg hardware acceleration arguments."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RecordConfig(FrigateBaseModel):
|
class RecordConfig(FrigateBaseModel):
|
||||||
|
|||||||
@ -523,6 +523,14 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
if camera_config.ffmpeg.hwaccel_args == "auto":
|
if camera_config.ffmpeg.hwaccel_args == "auto":
|
||||||
camera_config.ffmpeg.hwaccel_args = self.ffmpeg.hwaccel_args
|
camera_config.ffmpeg.hwaccel_args = self.ffmpeg.hwaccel_args
|
||||||
|
|
||||||
|
# Resolve export hwaccel_args: camera export -> camera ffmpeg -> global ffmpeg
|
||||||
|
# This allows per-camera override for exports (e.g., when camera resolution
|
||||||
|
# exceeds hardware encoder limits)
|
||||||
|
if camera_config.record.export.hwaccel_args == "auto":
|
||||||
|
camera_config.record.export.hwaccel_args = (
|
||||||
|
camera_config.ffmpeg.hwaccel_args
|
||||||
|
)
|
||||||
|
|
||||||
for input in camera_config.ffmpeg.inputs:
|
for input in camera_config.ffmpeg.inputs:
|
||||||
need_detect_dimensions = "detect" in input.roles and (
|
need_detect_dimensions = "detect" in input.roles and (
|
||||||
camera_config.detect.height is None
|
camera_config.detect.height is None
|
||||||
|
|||||||
@ -228,7 +228,7 @@ class RecordingExporter(threading.Thread):
|
|||||||
ffmpeg_cmd = (
|
ffmpeg_cmd = (
|
||||||
parse_preset_hardware_acceleration_encode(
|
parse_preset_hardware_acceleration_encode(
|
||||||
self.config.ffmpeg.ffmpeg_path,
|
self.config.ffmpeg.ffmpeg_path,
|
||||||
self.config.ffmpeg.hwaccel_args,
|
self.config.cameras[self.camera].record.export.hwaccel_args,
|
||||||
f"-an {ffmpeg_input}",
|
f"-an {ffmpeg_input}",
|
||||||
f"{self.config.cameras[self.camera].record.export.timelapse_args} -movflags +faststart",
|
f"{self.config.cameras[self.camera].record.export.timelapse_args} -movflags +faststart",
|
||||||
EncodeTypeEnum.timelapse,
|
EncodeTypeEnum.timelapse,
|
||||||
@ -319,7 +319,7 @@ class RecordingExporter(threading.Thread):
|
|||||||
ffmpeg_cmd = (
|
ffmpeg_cmd = (
|
||||||
parse_preset_hardware_acceleration_encode(
|
parse_preset_hardware_acceleration_encode(
|
||||||
self.config.ffmpeg.ffmpeg_path,
|
self.config.ffmpeg.ffmpeg_path,
|
||||||
self.config.ffmpeg.hwaccel_args,
|
self.config.cameras[self.camera].record.export.hwaccel_args,
|
||||||
f"{TIMELAPSE_DATA_INPUT_ARGS} {ffmpeg_input}",
|
f"{TIMELAPSE_DATA_INPUT_ARGS} {ffmpeg_input}",
|
||||||
f"{self.config.cameras[self.camera].record.export.timelapse_args} -movflags +faststart {video_path}",
|
f"{self.config.cameras[self.camera].record.export.timelapse_args} -movflags +faststart {video_path}",
|
||||||
EncodeTypeEnum.timelapse,
|
EncodeTypeEnum.timelapse,
|
||||||
|
|||||||
@ -22,6 +22,7 @@ from frigate.util.services import (
|
|||||||
get_bandwidth_stats,
|
get_bandwidth_stats,
|
||||||
get_cpu_stats,
|
get_cpu_stats,
|
||||||
get_fs_type,
|
get_fs_type,
|
||||||
|
get_hailo_temps,
|
||||||
get_intel_gpu_stats,
|
get_intel_gpu_stats,
|
||||||
get_jetson_stats,
|
get_jetson_stats,
|
||||||
get_nvidia_gpu_stats,
|
get_nvidia_gpu_stats,
|
||||||
@ -91,9 +92,76 @@ def get_temperatures() -> dict[str, float]:
|
|||||||
if temp is not None:
|
if temp is not None:
|
||||||
temps[apex] = temp
|
temps[apex] = temp
|
||||||
|
|
||||||
|
# Get temperatures for Hailo devices
|
||||||
|
temps.update(get_hailo_temps())
|
||||||
|
|
||||||
return temps
|
return temps
|
||||||
|
|
||||||
|
|
||||||
|
def get_detector_temperature(
|
||||||
|
detector_type: str,
|
||||||
|
detector_index_by_type: dict[str, int],
|
||||||
|
) -> Optional[float]:
|
||||||
|
"""Get temperature for a specific detector based on its type."""
|
||||||
|
if detector_type == "edgetpu":
|
||||||
|
# Get temperatures for all attached Corals
|
||||||
|
base = "/sys/class/apex/"
|
||||||
|
if os.path.isdir(base):
|
||||||
|
apex_devices = sorted(os.listdir(base))
|
||||||
|
index = detector_index_by_type.get("edgetpu", 0)
|
||||||
|
if index < len(apex_devices):
|
||||||
|
apex_name = apex_devices[index]
|
||||||
|
temp = read_temperature(os.path.join(base, apex_name, "temp"))
|
||||||
|
if temp is not None:
|
||||||
|
return temp
|
||||||
|
elif detector_type == "hailo8l":
|
||||||
|
# Get temperatures for Hailo devices
|
||||||
|
hailo_temps = get_hailo_temps()
|
||||||
|
if hailo_temps:
|
||||||
|
hailo_device_names = sorted(hailo_temps.keys())
|
||||||
|
index = detector_index_by_type.get("hailo8l", 0)
|
||||||
|
if index < len(hailo_device_names):
|
||||||
|
device_name = hailo_device_names[index]
|
||||||
|
return hailo_temps[device_name]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_detector_stats(
|
||||||
|
stats_tracking: StatsTrackingTypes,
|
||||||
|
) -> dict[str, dict[str, Any]]:
|
||||||
|
"""Get stats for all detectors, including temperatures based on detector type."""
|
||||||
|
detector_stats: dict[str, dict[str, Any]] = {}
|
||||||
|
detector_type_indices: dict[str, int] = {}
|
||||||
|
|
||||||
|
for name, detector in stats_tracking["detectors"].items():
|
||||||
|
pid = detector.detect_process.pid if detector.detect_process else None
|
||||||
|
detector_type = detector.detector_config.type
|
||||||
|
|
||||||
|
# Keep track of the index for each detector type to match temperatures correctly
|
||||||
|
current_index = detector_type_indices.get(detector_type, 0)
|
||||||
|
detector_type_indices[detector_type] = current_index + 1
|
||||||
|
|
||||||
|
detector_stat = {
|
||||||
|
"inference_speed": round(detector.avg_inference_speed.value * 1000, 2), # type: ignore[attr-defined]
|
||||||
|
# issue https://github.com/python/typeshed/issues/8799
|
||||||
|
# from mypy 0.981 onwards
|
||||||
|
"detection_start": detector.detection_start.value, # type: ignore[attr-defined]
|
||||||
|
# issue https://github.com/python/typeshed/issues/8799
|
||||||
|
# from mypy 0.981 onwards
|
||||||
|
"pid": pid,
|
||||||
|
}
|
||||||
|
|
||||||
|
temp = get_detector_temperature(detector_type, {detector_type: current_index})
|
||||||
|
|
||||||
|
if temp is not None:
|
||||||
|
detector_stat["temperature"] = round(temp, 1)
|
||||||
|
|
||||||
|
detector_stats[name] = detector_stat
|
||||||
|
|
||||||
|
return detector_stats
|
||||||
|
|
||||||
|
|
||||||
def get_processing_stats(
|
def get_processing_stats(
|
||||||
config: FrigateConfig, stats: dict[str, str], hwaccel_errors: list[str]
|
config: FrigateConfig, stats: dict[str, str], hwaccel_errors: list[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -319,18 +387,7 @@ def stats_snapshot(
|
|||||||
**connection_quality,
|
**connection_quality,
|
||||||
}
|
}
|
||||||
|
|
||||||
stats["detectors"] = {}
|
stats["detectors"] = get_detector_stats(stats_tracking)
|
||||||
for name, detector in stats_tracking["detectors"].items():
|
|
||||||
pid = detector.detect_process.pid if detector.detect_process else None
|
|
||||||
stats["detectors"][name] = {
|
|
||||||
"inference_speed": round(detector.avg_inference_speed.value * 1000, 2), # type: ignore[attr-defined]
|
|
||||||
# issue https://github.com/python/typeshed/issues/8799
|
|
||||||
# from mypy 0.981 onwards
|
|
||||||
"detection_start": detector.detection_start.value, # type: ignore[attr-defined]
|
|
||||||
# issue https://github.com/python/typeshed/issues/8799
|
|
||||||
# from mypy 0.981 onwards
|
|
||||||
"pid": pid,
|
|
||||||
}
|
|
||||||
stats["camera_fps"] = round(total_camera_fps, 2)
|
stats["camera_fps"] = round(total_camera_fps, 2)
|
||||||
stats["process_fps"] = round(total_process_fps, 2)
|
stats["process_fps"] = round(total_process_fps, 2)
|
||||||
stats["skipped_fps"] = round(total_skipped_fps, 2)
|
stats["skipped_fps"] = round(total_skipped_fps, 2)
|
||||||
@ -416,7 +473,6 @@ def stats_snapshot(
|
|||||||
"version": VERSION,
|
"version": VERSION,
|
||||||
"latest_version": stats_tracking["latest_frigate_version"],
|
"latest_version": stats_tracking["latest_frigate_version"],
|
||||||
"storage": {},
|
"storage": {},
|
||||||
"temperatures": get_temperatures(),
|
|
||||||
"last_updated": int(time.time()),
|
"last_updated": int(time.time()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -549,6 +549,53 @@ def get_jetson_stats() -> Optional[dict[int, dict]]:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def get_hailo_temps() -> dict[str, float]:
|
||||||
|
"""Get temperatures for Hailo devices."""
|
||||||
|
try:
|
||||||
|
from hailo_platform import Device
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
temps = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
device_ids = Device.scan()
|
||||||
|
for i, device_id in enumerate(device_ids):
|
||||||
|
try:
|
||||||
|
with Device(device_id) as device:
|
||||||
|
temp_info = device.control.get_chip_temperature()
|
||||||
|
|
||||||
|
# Get board name and normalise it
|
||||||
|
identity = device.control.identify()
|
||||||
|
board_name = None
|
||||||
|
for line in str(identity).split("\n"):
|
||||||
|
if line.startswith("Board Name:"):
|
||||||
|
board_name = (
|
||||||
|
line.split(":", 1)[1].strip().lower().replace("-", "")
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
if not board_name:
|
||||||
|
board_name = f"hailo{i}"
|
||||||
|
|
||||||
|
# Use indexed name if multiple devices, otherwise just the board name
|
||||||
|
device_name = (
|
||||||
|
f"{board_name}-{i}" if len(device_ids) > 1 else board_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# ts1_temperature is also available, but appeared to be the same as ts0 in testing.
|
||||||
|
temps[device_name] = round(temp_info.ts0_temperature, 1)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(
|
||||||
|
f"Failed to get temperature for Hailo device {device_id}: {e}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Failed to scan for Hailo devices: {e}")
|
||||||
|
|
||||||
|
return temps
|
||||||
|
|
||||||
|
|
||||||
def ffprobe_stream(ffmpeg, path: str, detailed: bool = False) -> sp.CompletedProcess:
|
def ffprobe_stream(ffmpeg, path: str, detailed: bool = False) -> sp.CompletedProcess:
|
||||||
"""Run ffprobe on stream."""
|
"""Run ffprobe on stream."""
|
||||||
clean_path = escape_special_characters(path)
|
clean_path = escape_special_characters(path)
|
||||||
|
|||||||
@ -41,6 +41,7 @@ export type DetectorStats = {
|
|||||||
detection_start: number;
|
detection_start: number;
|
||||||
inference_speed: number;
|
inference_speed: number;
|
||||||
pid: number;
|
pid: number;
|
||||||
|
temperature?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EmbeddingsStats = {
|
export type EmbeddingsStats = {
|
||||||
@ -72,7 +73,6 @@ export type GpuInfo = "vainfo" | "nvinfo";
|
|||||||
export type ServiceStats = {
|
export type ServiceStats = {
|
||||||
last_updated: number;
|
last_updated: number;
|
||||||
storage: { [path: string]: StorageStats };
|
storage: { [path: string]: StorageStats };
|
||||||
temperatures: { [apex: string]: number };
|
|
||||||
uptime: number;
|
uptime: number;
|
||||||
latest_version: string;
|
latest_version: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
|||||||
@ -127,13 +127,6 @@ export default function GeneralMetrics({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
statsHistory.length > 0 &&
|
|
||||||
Object.keys(statsHistory[0].service.temperatures).length == 0
|
|
||||||
) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const series: {
|
const series: {
|
||||||
[key: string]: { name: string; data: { x: number; y: number }[] };
|
[key: string]: { name: string; data: { x: number; y: number }[] };
|
||||||
} = {};
|
} = {};
|
||||||
@ -143,22 +136,22 @@ export default function GeneralMetrics({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.entries(stats.detectors).forEach(([key], cIdx) => {
|
Object.entries(stats.detectors).forEach(([key, detectorStats]) => {
|
||||||
if (!key.includes("coral")) {
|
if (detectorStats.temperature === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cIdx <= Object.keys(stats.service.temperatures).length) {
|
if (!(key in series)) {
|
||||||
if (!(key in series)) {
|
series[key] = {
|
||||||
series[key] = {
|
name: key,
|
||||||
name: key,
|
data: [],
|
||||||
data: [],
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const temp = Object.values(stats.service.temperatures)[cIdx];
|
|
||||||
series[key].data.push({ x: statsIdx + 1, y: Math.round(temp) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
series[key].data.push({
|
||||||
|
x: statsIdx + 1,
|
||||||
|
y: Math.round(detectorStats.temperature),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user