mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-02-05 00:15:51 +01:00
Add ffmpeg config to increase HEVC compatibility with Apple devices (#15795)
* Add config option for handling HEVC playback on Apple devices * Update docs * Remove unused
This commit is contained in:
parent
dff6a20764
commit
f2cc16bf3c
@ -67,14 +67,15 @@ ffmpeg:
|
|||||||
|
|
||||||
### Annke C800
|
### Annke C800
|
||||||
|
|
||||||
This camera is H.265 only. To be able to play clips on some devices (like MacOs or iPhone) the H.265 stream has to be repackaged and the audio stream has to be converted to aac. Unfortunately direct playback of in the browser is not working (yet), but the downloaded clip can be played locally.
|
This camera is H.265 only. To be able to play clips on some devices (like MacOs or iPhone) the H.265 stream has to be adjusted using the `apple_compatibility` config.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
cameras:
|
cameras:
|
||||||
annkec800: # <------ Name the camera
|
annkec800: # <------ Name the camera
|
||||||
ffmpeg:
|
ffmpeg:
|
||||||
|
apple_compatibility: true # <- Adds compatibility with MacOS and iPhone
|
||||||
output_args:
|
output_args:
|
||||||
record: -f segment -segment_time 10 -segment_format mp4 -reset_timestamps 1 -strftime 1 -c:v copy -tag:v hvc1 -bsf:v hevc_mp4toannexb -c:a aac
|
record: preset-record-generic-audio-aac
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
- path: rtsp://user:password@camera-ip:554/H264/ch1/main/av_stream # <----- Update for your camera
|
- path: rtsp://user:password@camera-ip:554/H264/ch1/main/av_stream # <----- Update for your camera
|
||||||
|
@ -244,6 +244,8 @@ ffmpeg:
|
|||||||
# If set too high, then if a ffmpeg crash or camera stream timeout occurs, you could potentially lose up to a maximum of retry_interval second(s) of footage
|
# If set too high, then if a ffmpeg crash or camera stream timeout occurs, you could potentially lose up to a maximum of retry_interval second(s) of footage
|
||||||
# NOTE: this can be a useful setting for Wireless / Battery cameras to reduce how much footage is potentially lost during a connection timeout.
|
# NOTE: this can be a useful setting for Wireless / Battery cameras to reduce how much footage is potentially lost during a connection timeout.
|
||||||
retry_interval: 10
|
retry_interval: 10
|
||||||
|
# Optional: Set tag on HEVC (H.265) recording stream to improve compatibility with Apple players. (default: shown below)
|
||||||
|
apple_compatibility: false
|
||||||
|
|
||||||
# Optional: Detect configuration
|
# Optional: Detect configuration
|
||||||
# NOTE: Can be overridden at the camera level
|
# NOTE: Can be overridden at the camera level
|
||||||
|
@ -167,7 +167,7 @@ class CameraConfig(FrigateBaseModel):
|
|||||||
record_args = get_ffmpeg_arg_list(
|
record_args = get_ffmpeg_arg_list(
|
||||||
parse_preset_output_record(
|
parse_preset_output_record(
|
||||||
self.ffmpeg.output_args.record,
|
self.ffmpeg.output_args.record,
|
||||||
self.ffmpeg.output_args._force_record_hvc1,
|
self.ffmpeg.apple_compatibility,
|
||||||
)
|
)
|
||||||
or self.ffmpeg.output_args.record
|
or self.ffmpeg.output_args.record
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,7 @@ import shutil
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from pydantic import Field, PrivateAttr, field_validator
|
from pydantic import Field, field_validator
|
||||||
|
|
||||||
from frigate.const import DEFAULT_FFMPEG_VERSION, INCLUDED_FFMPEG_VERSIONS
|
from frigate.const import DEFAULT_FFMPEG_VERSION, INCLUDED_FFMPEG_VERSIONS
|
||||||
|
|
||||||
@ -42,7 +42,6 @@ class FfmpegOutputArgsConfig(FrigateBaseModel):
|
|||||||
default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
||||||
title="Record role FFmpeg output arguments.",
|
title="Record role FFmpeg output arguments.",
|
||||||
)
|
)
|
||||||
_force_record_hvc1: bool = PrivateAttr(default=False)
|
|
||||||
|
|
||||||
|
|
||||||
class FfmpegConfig(FrigateBaseModel):
|
class FfmpegConfig(FrigateBaseModel):
|
||||||
@ -64,6 +63,10 @@ class FfmpegConfig(FrigateBaseModel):
|
|||||||
default=10.0,
|
default=10.0,
|
||||||
title="Time in seconds to wait before FFmpeg retries connecting to the camera.",
|
title="Time in seconds to wait before FFmpeg retries connecting to the camera.",
|
||||||
)
|
)
|
||||||
|
apple_compatibility: bool = Field(
|
||||||
|
default=False,
|
||||||
|
title="Set tag on HEVC (H.265) recording stream to improve compatibility with Apple players.",
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ffmpeg_path(self) -> str:
|
def ffmpeg_path(self) -> str:
|
||||||
|
@ -458,13 +458,12 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
camera_config.ffmpeg.hwaccel_args = self.ffmpeg.hwaccel_args
|
camera_config.ffmpeg.hwaccel_args = self.ffmpeg.hwaccel_args
|
||||||
|
|
||||||
for input in camera_config.ffmpeg.inputs:
|
for input in camera_config.ffmpeg.inputs:
|
||||||
need_record_fourcc = False and "record" in input.roles
|
|
||||||
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
|
||||||
or camera_config.detect.width is None
|
or camera_config.detect.width is None
|
||||||
)
|
)
|
||||||
|
|
||||||
if need_detect_dimensions or need_record_fourcc:
|
if need_detect_dimensions:
|
||||||
stream_info = {"width": 0, "height": 0, "fourcc": None}
|
stream_info = {"width": 0, "height": 0, "fourcc": None}
|
||||||
try:
|
try:
|
||||||
stream_info = stream_info_retriever.get_stream_info(
|
stream_info = stream_info_retriever.get_stream_info(
|
||||||
@ -488,14 +487,6 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
else DEFAULT_DETECT_DIMENSIONS["height"]
|
else DEFAULT_DETECT_DIMENSIONS["height"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if need_record_fourcc:
|
|
||||||
# Apple only supports HEVC if it is hvc1 (vs. hev1)
|
|
||||||
camera_config.ffmpeg.output_args._force_record_hvc1 = (
|
|
||||||
stream_info["fourcc"] == "hevc"
|
|
||||||
if stream_info.get("hevc")
|
|
||||||
else False
|
|
||||||
)
|
|
||||||
|
|
||||||
# Warn if detect fps > 10
|
# Warn if detect fps > 10
|
||||||
if camera_config.detect.fps > 10:
|
if camera_config.detect.fps > 10:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
@ -65,6 +65,7 @@ INCLUDED_FFMPEG_VERSIONS = ["7.0", "5.0"]
|
|||||||
FFMPEG_HWACCEL_NVIDIA = "preset-nvidia"
|
FFMPEG_HWACCEL_NVIDIA = "preset-nvidia"
|
||||||
FFMPEG_HWACCEL_VAAPI = "preset-vaapi"
|
FFMPEG_HWACCEL_VAAPI = "preset-vaapi"
|
||||||
FFMPEG_HWACCEL_VULKAN = "preset-vulkan"
|
FFMPEG_HWACCEL_VULKAN = "preset-vulkan"
|
||||||
|
FFMPEG_HVC1_ARGS = ["-tag:v", "hvc1"]
|
||||||
|
|
||||||
# Regex constants
|
# Regex constants
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ from enum import Enum
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from frigate.const import (
|
from frigate.const import (
|
||||||
|
FFMPEG_HVC1_ARGS,
|
||||||
FFMPEG_HWACCEL_NVIDIA,
|
FFMPEG_HWACCEL_NVIDIA,
|
||||||
FFMPEG_HWACCEL_VAAPI,
|
FFMPEG_HWACCEL_VAAPI,
|
||||||
FFMPEG_HWACCEL_VULKAN,
|
FFMPEG_HWACCEL_VULKAN,
|
||||||
@ -490,6 +491,6 @@ def parse_preset_output_record(arg: Any, force_record_hvc1: bool) -> list[str]:
|
|||||||
|
|
||||||
if force_record_hvc1:
|
if force_record_hvc1:
|
||||||
# Apple only supports HEVC if it is hvc1 (vs. hev1)
|
# Apple only supports HEVC if it is hvc1 (vs. hev1)
|
||||||
preset += ["-tag:v", "hvc1"]
|
preset += FFMPEG_HVC1_ARGS
|
||||||
|
|
||||||
return preset
|
return preset
|
||||||
|
@ -19,6 +19,7 @@ from frigate.const import (
|
|||||||
CACHE_DIR,
|
CACHE_DIR,
|
||||||
CLIPS_DIR,
|
CLIPS_DIR,
|
||||||
EXPORT_DIR,
|
EXPORT_DIR,
|
||||||
|
FFMPEG_HVC1_ARGS,
|
||||||
MAX_PLAYLIST_SECONDS,
|
MAX_PLAYLIST_SECONDS,
|
||||||
PREVIEW_FRAME_TYPE,
|
PREVIEW_FRAME_TYPE,
|
||||||
)
|
)
|
||||||
@ -219,7 +220,7 @@ class RecordingExporter(threading.Thread):
|
|||||||
|
|
||||||
if self.playback_factor == PlaybackFactorEnum.realtime:
|
if self.playback_factor == PlaybackFactorEnum.realtime:
|
||||||
ffmpeg_cmd = (
|
ffmpeg_cmd = (
|
||||||
f"{self.config.ffmpeg.ffmpeg_path} -hide_banner {ffmpeg_input} -c copy -movflags +faststart {video_path}"
|
f"{self.config.ffmpeg.ffmpeg_path} -hide_banner {ffmpeg_input} -c copy -movflags +faststart"
|
||||||
).split(" ")
|
).split(" ")
|
||||||
elif self.playback_factor == PlaybackFactorEnum.timelapse_25x:
|
elif self.playback_factor == PlaybackFactorEnum.timelapse_25x:
|
||||||
ffmpeg_cmd = (
|
ffmpeg_cmd = (
|
||||||
@ -227,11 +228,16 @@ class RecordingExporter(threading.Thread):
|
|||||||
self.config.ffmpeg.ffmpeg_path,
|
self.config.ffmpeg.ffmpeg_path,
|
||||||
self.config.ffmpeg.hwaccel_args,
|
self.config.ffmpeg.hwaccel_args,
|
||||||
f"-an {ffmpeg_input}",
|
f"-an {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",
|
||||||
EncodeTypeEnum.timelapse,
|
EncodeTypeEnum.timelapse,
|
||||||
)
|
)
|
||||||
).split(" ")
|
).split(" ")
|
||||||
|
|
||||||
|
if self.config.ffmpeg.apple_compatibility:
|
||||||
|
ffmpeg_cmd += FFMPEG_HVC1_ARGS
|
||||||
|
|
||||||
|
ffmpeg_cmd.append(video_path)
|
||||||
|
|
||||||
return ffmpeg_cmd, playlist_lines
|
return ffmpeg_cmd, playlist_lines
|
||||||
|
|
||||||
def get_preview_export_command(self, video_path: str) -> list[str]:
|
def get_preview_export_command(self, video_path: str) -> list[str]:
|
||||||
|
Loading…
Reference in New Issue
Block a user