Fix iOS playback of H.265 clips (#10105)

* Fix iOS playback of H.265 clips

* CI
This commit is contained in:
Andrew Reiter 2024-02-27 22:41:36 -05:00 committed by GitHub
parent 9893741990
commit 5edaaceaf2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 60 additions and 26 deletions

View File

@ -614,6 +614,7 @@ 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):
@ -878,7 +879,10 @@ class CameraConfig(FrigateBaseModel):
if "record" in ffmpeg_input.roles and self.record.enabled: if "record" in ffmpeg_input.roles and self.record.enabled:
record_args = get_ffmpeg_arg_list( record_args = get_ffmpeg_arg_list(
parse_preset_output_record(self.ffmpeg.output_args.record) parse_preset_output_record(
self.ffmpeg.output_args.record,
self.ffmpeg.output_args._force_record_hvc1,
)
or self.ffmpeg.output_args.record or self.ffmpeg.output_args.record
) )
@ -1161,21 +1165,24 @@ class FrigateConfig(FrigateBaseModel):
if camera_config.ffmpeg.hwaccel_args == "auto": if camera_config.ffmpeg.hwaccel_args == "auto":
camera_config.ffmpeg.hwaccel_args = config.ffmpeg.hwaccel_args camera_config.ffmpeg.hwaccel_args = config.ffmpeg.hwaccel_args
if ( for input in camera_config.ffmpeg.inputs:
need_record_fourcc = "record" in input.roles
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
): )
for input in camera_config.ffmpeg.inputs:
if "detect" in input.roles: if need_detect_dimensions or need_record_fourcc:
stream_info = {"width": 0, "height": 0} stream_info = {"width": 0, "height": 0, "fourcc": None}
try: try:
stream_info = asyncio.run(get_video_properties(input.path)) stream_info = asyncio.run(get_video_properties(input.path))
except Exception: except Exception:
logger.warn( logger.warn(
f"Error detecting stream resolution automatically for {input.path} Applying default values." f"Error detecting stream parameters automatically for {input.path} Applying default values."
) )
stream_info = {"width": 0, "height": 0} stream_info = {"width": 0, "height": 0, "fourcc": None}
if need_detect_dimensions:
camera_config.detect.width = ( camera_config.detect.width = (
stream_info["width"] stream_info["width"]
if stream_info.get("width") if stream_info.get("width")
@ -1187,6 +1194,14 @@ 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
)
# Default min_initialized configuration # Default min_initialized configuration
min_initialized = camera_config.detect.fps / 2 min_initialized = camera_config.detect.fps / 2
if camera_config.detect.min_initialized is None: if camera_config.detect.min_initialized is None:

View File

@ -461,9 +461,18 @@ PRESETS_RECORD_OUTPUT = {
} }
def parse_preset_output_record(arg: Any) -> list[str]: def parse_preset_output_record(arg: Any, force_record_hvc1: bool) -> list[str]:
"""Return the correct preset if in preset format otherwise return None.""" """Return the correct preset if in preset format otherwise return None."""
if not isinstance(arg, str): if not isinstance(arg, str):
return None return None
return PRESETS_RECORD_OUTPUT.get(arg, None) preset = PRESETS_RECORD_OUTPUT.get(arg, None)
if not preset:
return None
if force_record_hvc1:
# Apple only supports HEVC if it is hvc1 (vs. hev1)
preset += ["-tag:v", "hvc1"]
return preset

View File

@ -505,10 +505,20 @@ async def get_video_properties(url, get_duration=False) -> dict[str, any]:
# Get the height of frames in the video stream # Get the height of frames in the video stream
height = video.get(cv2.CAP_PROP_FRAME_HEIGHT) height = video.get(cv2.CAP_PROP_FRAME_HEIGHT)
# Get the stream encoding
fourcc_int = int(video.get(cv2.CAP_PROP_FOURCC))
fourcc = (
chr((fourcc_int >> 0) & 255)
+ chr((fourcc_int >> 8) & 255)
+ chr((fourcc_int >> 16) & 255)
+ chr((fourcc_int >> 24) & 255)
)
# Release the video stream # Release the video stream
video.release() video.release()
result["width"] = round(width) result["width"] = round(width)
result["height"] = round(height) result["height"] = round(height)
result["fourcc"] = fourcc
return result return result