Support multiple camera streams in HomeKit (#37968)
* Support multiple camera stream in HomeKit
* Update homeassistant/components/homekit/type_cameras.py
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
* Revert "Update homeassistant/components/homekit/type_cameras.py"
This reverts commit d7624c5bff
.
* Update homeassistant/components/homekit/type_cameras.py
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
* Update homeassistant/components/homekit/type_cameras.py
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
* black
* bump pyhap
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
pull/38212/head
parent
bbc8748e3b
commit
3206f4dc83
|
@ -55,6 +55,7 @@ CONF_SUPPORT_AUDIO = "support_audio"
|
|||
CONF_VIDEO_CODEC = "video_codec"
|
||||
CONF_VIDEO_MAP = "video_map"
|
||||
CONF_VIDEO_PACKET_SIZE = "video_packet_size"
|
||||
CONF_STREAM_COUNT = "stream_count"
|
||||
|
||||
# #### Config Defaults ####
|
||||
DEFAULT_SUPPORT_AUDIO = False
|
||||
|
@ -72,6 +73,7 @@ DEFAULT_SAFE_MODE = False
|
|||
DEFAULT_VIDEO_CODEC = VIDEO_CODEC_LIBX264
|
||||
DEFAULT_VIDEO_MAP = "0:v:0"
|
||||
DEFAULT_VIDEO_PACKET_SIZE = 1316
|
||||
DEFAULT_STREAM_COUNT = 3
|
||||
|
||||
# #### Features ####
|
||||
FEATURE_ON_OFF = "on_off"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "HomeKit",
|
||||
"documentation": "https://www.home-assistant.io/integrations/homekit",
|
||||
"requirements": [
|
||||
"HAP-python==2.9.2",
|
||||
"HAP-python==3.0.0",
|
||||
"fnvhash==0.1.0",
|
||||
"PyQRCode==1.2.1",
|
||||
"base36==0.1.1",
|
||||
|
|
|
@ -5,7 +5,6 @@ import logging
|
|||
|
||||
from haffmpeg.core import HAFFmpeg
|
||||
from pyhap.camera import (
|
||||
STREAMING_STATUS,
|
||||
VIDEO_CODEC_PARAM_LEVEL_TYPES,
|
||||
VIDEO_CODEC_PARAM_PROFILE_ID_TYPES,
|
||||
Camera as PyhapCamera,
|
||||
|
@ -24,7 +23,6 @@ from homeassistant.util import get_local_ip
|
|||
from .accessories import TYPES, HomeAccessory
|
||||
from .const import (
|
||||
CHAR_MOTION_DETECTED,
|
||||
CHAR_STREAMING_STRATUS,
|
||||
CONF_AUDIO_CODEC,
|
||||
CONF_AUDIO_MAP,
|
||||
CONF_AUDIO_PACKET_SIZE,
|
||||
|
@ -33,6 +31,7 @@ from .const import (
|
|||
CONF_MAX_HEIGHT,
|
||||
CONF_MAX_WIDTH,
|
||||
CONF_STREAM_ADDRESS,
|
||||
CONF_STREAM_COUNT,
|
||||
CONF_STREAM_SOURCE,
|
||||
CONF_SUPPORT_AUDIO,
|
||||
CONF_VIDEO_CODEC,
|
||||
|
@ -44,11 +43,11 @@ from .const import (
|
|||
DEFAULT_MAX_FPS,
|
||||
DEFAULT_MAX_HEIGHT,
|
||||
DEFAULT_MAX_WIDTH,
|
||||
DEFAULT_STREAM_COUNT,
|
||||
DEFAULT_SUPPORT_AUDIO,
|
||||
DEFAULT_VIDEO_CODEC,
|
||||
DEFAULT_VIDEO_MAP,
|
||||
DEFAULT_VIDEO_PACKET_SIZE,
|
||||
SERV_CAMERA_RTP_STREAM_MANAGEMENT,
|
||||
SERV_MOTION_SENSOR,
|
||||
)
|
||||
from .img_util import scale_jpeg_camera_image
|
||||
|
@ -121,6 +120,7 @@ CONFIG_DEFAULTS = {
|
|||
CONF_VIDEO_CODEC: DEFAULT_VIDEO_CODEC,
|
||||
CONF_AUDIO_PACKET_SIZE: DEFAULT_AUDIO_PACKET_SIZE,
|
||||
CONF_VIDEO_PACKET_SIZE: DEFAULT_VIDEO_PACKET_SIZE,
|
||||
CONF_STREAM_COUNT: DEFAULT_STREAM_COUNT,
|
||||
}
|
||||
|
||||
|
||||
|
@ -131,7 +131,6 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||
def __init__(self, hass, driver, name, entity_id, aid, config):
|
||||
"""Initialize a Camera accessory object."""
|
||||
self._ffmpeg = hass.data[DATA_FFMPEG]
|
||||
self._cur_session = None
|
||||
for config_key in CONFIG_DEFAULTS:
|
||||
if config_key not in config:
|
||||
config[config_key] = CONFIG_DEFAULTS[config_key]
|
||||
|
@ -178,6 +177,7 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||
"audio": audio_options,
|
||||
"address": stream_address,
|
||||
"srtp": True,
|
||||
"stream_count": config[CONF_STREAM_COUNT],
|
||||
}
|
||||
|
||||
super().__init__(
|
||||
|
@ -313,51 +313,42 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||
if not opened:
|
||||
_LOGGER.error("Failed to open ffmpeg stream")
|
||||
return False
|
||||
session_info["stream"] = stream
|
||||
|
||||
_LOGGER.info(
|
||||
"[%s] Started stream process - PID %d",
|
||||
session_info["id"],
|
||||
stream.process.pid,
|
||||
)
|
||||
|
||||
ffmpeg_watcher = async_track_time_interval(
|
||||
self.hass, self._async_ffmpeg_watch, FFMPEG_WATCH_INTERVAL
|
||||
session_info["stream"] = stream
|
||||
session_info[FFMPEG_PID] = stream.process.pid
|
||||
|
||||
async def watch_session(_):
|
||||
await self._async_ffmpeg_watch(session_info["id"])
|
||||
|
||||
session_info[FFMPEG_WATCHER] = async_track_time_interval(
|
||||
self.hass, watch_session, FFMPEG_WATCH_INTERVAL,
|
||||
)
|
||||
self._cur_session = {
|
||||
FFMPEG_WATCHER: ffmpeg_watcher,
|
||||
FFMPEG_PID: stream.process.pid,
|
||||
SESSION_ID: session_info["id"],
|
||||
}
|
||||
|
||||
return await self._async_ffmpeg_watch(0)
|
||||
return await self._async_ffmpeg_watch(session_info["id"])
|
||||
|
||||
async def _async_ffmpeg_watch(self, _):
|
||||
async def _async_ffmpeg_watch(self, session_id):
|
||||
"""Check to make sure ffmpeg is still running and cleanup if not."""
|
||||
ffmpeg_pid = self._cur_session[FFMPEG_PID]
|
||||
session_id = self._cur_session[SESSION_ID]
|
||||
ffmpeg_pid = self.sessions[session_id][FFMPEG_PID]
|
||||
if pid_is_alive(ffmpeg_pid):
|
||||
return True
|
||||
|
||||
_LOGGER.warning("Streaming process ended unexpectedly - PID %d", ffmpeg_pid)
|
||||
self._async_stop_ffmpeg_watch()
|
||||
self._async_set_streaming_available(session_id)
|
||||
self._async_stop_ffmpeg_watch(session_id)
|
||||
self.set_streaming_available(self.sessions[session_id]["stream_idx"])
|
||||
return False
|
||||
|
||||
@callback
|
||||
def _async_stop_ffmpeg_watch(self):
|
||||
def _async_stop_ffmpeg_watch(self, session_id):
|
||||
"""Cleanup a streaming session after stopping."""
|
||||
if not self._cur_session:
|
||||
if FFMPEG_WATCHER not in self.sessions[session_id]:
|
||||
return
|
||||
self._cur_session[FFMPEG_WATCHER]()
|
||||
self._cur_session = None
|
||||
|
||||
@callback
|
||||
def _async_set_streaming_available(self, session_id):
|
||||
"""Free the session so they can start another."""
|
||||
self.streaming_status = STREAMING_STATUS["AVAILABLE"]
|
||||
self.get_service(SERV_CAMERA_RTP_STREAM_MANAGEMENT).get_characteristic(
|
||||
CHAR_STREAMING_STRATUS
|
||||
).notify()
|
||||
self.sessions[session_id].pop(FFMPEG_WATCHER)()
|
||||
|
||||
async def stop_stream(self, session_info):
|
||||
"""Stop the stream for the given ``session_id``."""
|
||||
|
@ -367,7 +358,7 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||
_LOGGER.debug("No stream for session ID %s", session_id)
|
||||
return
|
||||
|
||||
self._async_stop_ffmpeg_watch()
|
||||
self._async_stop_ffmpeg_watch(session_id)
|
||||
|
||||
if not pid_is_alive(stream.process.pid):
|
||||
_LOGGER.info("[%s] Stream already stopped", session_id)
|
||||
|
|
|
@ -41,6 +41,7 @@ from .const import (
|
|||
CONF_MAX_HEIGHT,
|
||||
CONF_MAX_WIDTH,
|
||||
CONF_STREAM_ADDRESS,
|
||||
CONF_STREAM_COUNT,
|
||||
CONF_STREAM_SOURCE,
|
||||
CONF_SUPPORT_AUDIO,
|
||||
CONF_VIDEO_CODEC,
|
||||
|
@ -53,6 +54,7 @@ from .const import (
|
|||
DEFAULT_MAX_FPS,
|
||||
DEFAULT_MAX_HEIGHT,
|
||||
DEFAULT_MAX_WIDTH,
|
||||
DEFAULT_STREAM_COUNT,
|
||||
DEFAULT_SUPPORT_AUDIO,
|
||||
DEFAULT_VIDEO_CODEC,
|
||||
DEFAULT_VIDEO_MAP,
|
||||
|
@ -112,6 +114,9 @@ CAMERA_SCHEMA = BASIC_INFO_SCHEMA.extend(
|
|||
vol.Optional(CONF_MAX_FPS, default=DEFAULT_MAX_FPS): cv.positive_int,
|
||||
vol.Optional(CONF_AUDIO_MAP, default=DEFAULT_AUDIO_MAP): cv.string,
|
||||
vol.Optional(CONF_VIDEO_MAP, default=DEFAULT_VIDEO_MAP): cv.string,
|
||||
vol.Optional(CONF_STREAM_COUNT, default=DEFAULT_STREAM_COUNT): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=1, max=10)
|
||||
),
|
||||
vol.Optional(CONF_VIDEO_CODEC, default=DEFAULT_VIDEO_CODEC): vol.In(
|
||||
VALID_VIDEO_CODECS
|
||||
),
|
||||
|
|
|
@ -18,7 +18,7 @@ Adafruit-SHT31==1.0.2
|
|||
# Adafruit_BBIO==1.1.1
|
||||
|
||||
# homeassistant.components.homekit
|
||||
HAP-python==2.9.2
|
||||
HAP-python==3.0.0
|
||||
|
||||
# homeassistant.components.mastodon
|
||||
Mastodon.py==1.5.1
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
-r requirements_test.txt
|
||||
|
||||
# homeassistant.components.homekit
|
||||
HAP-python==2.9.2
|
||||
HAP-python==3.0.0
|
||||
|
||||
# homeassistant.components.plugwise
|
||||
Plugwise_Smile==1.1.0
|
||||
|
|
Loading…
Reference in New Issue