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
J. Nick Koston 2020-07-25 07:12:14 -10:00 committed by GitHub
parent bbc8748e3b
commit 3206f4dc83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 32 additions and 34 deletions

View File

@ -55,6 +55,7 @@ CONF_SUPPORT_AUDIO = "support_audio"
CONF_VIDEO_CODEC = "video_codec" CONF_VIDEO_CODEC = "video_codec"
CONF_VIDEO_MAP = "video_map" CONF_VIDEO_MAP = "video_map"
CONF_VIDEO_PACKET_SIZE = "video_packet_size" CONF_VIDEO_PACKET_SIZE = "video_packet_size"
CONF_STREAM_COUNT = "stream_count"
# #### Config Defaults #### # #### Config Defaults ####
DEFAULT_SUPPORT_AUDIO = False DEFAULT_SUPPORT_AUDIO = False
@ -72,6 +73,7 @@ DEFAULT_SAFE_MODE = False
DEFAULT_VIDEO_CODEC = VIDEO_CODEC_LIBX264 DEFAULT_VIDEO_CODEC = VIDEO_CODEC_LIBX264
DEFAULT_VIDEO_MAP = "0:v:0" DEFAULT_VIDEO_MAP = "0:v:0"
DEFAULT_VIDEO_PACKET_SIZE = 1316 DEFAULT_VIDEO_PACKET_SIZE = 1316
DEFAULT_STREAM_COUNT = 3
# #### Features #### # #### Features ####
FEATURE_ON_OFF = "on_off" FEATURE_ON_OFF = "on_off"

View File

@ -3,7 +3,7 @@
"name": "HomeKit", "name": "HomeKit",
"documentation": "https://www.home-assistant.io/integrations/homekit", "documentation": "https://www.home-assistant.io/integrations/homekit",
"requirements": [ "requirements": [
"HAP-python==2.9.2", "HAP-python==3.0.0",
"fnvhash==0.1.0", "fnvhash==0.1.0",
"PyQRCode==1.2.1", "PyQRCode==1.2.1",
"base36==0.1.1", "base36==0.1.1",

View File

@ -5,7 +5,6 @@ import logging
from haffmpeg.core import HAFFmpeg from haffmpeg.core import HAFFmpeg
from pyhap.camera import ( from pyhap.camera import (
STREAMING_STATUS,
VIDEO_CODEC_PARAM_LEVEL_TYPES, VIDEO_CODEC_PARAM_LEVEL_TYPES,
VIDEO_CODEC_PARAM_PROFILE_ID_TYPES, VIDEO_CODEC_PARAM_PROFILE_ID_TYPES,
Camera as PyhapCamera, Camera as PyhapCamera,
@ -24,7 +23,6 @@ from homeassistant.util import get_local_ip
from .accessories import TYPES, HomeAccessory from .accessories import TYPES, HomeAccessory
from .const import ( from .const import (
CHAR_MOTION_DETECTED, CHAR_MOTION_DETECTED,
CHAR_STREAMING_STRATUS,
CONF_AUDIO_CODEC, CONF_AUDIO_CODEC,
CONF_AUDIO_MAP, CONF_AUDIO_MAP,
CONF_AUDIO_PACKET_SIZE, CONF_AUDIO_PACKET_SIZE,
@ -33,6 +31,7 @@ from .const import (
CONF_MAX_HEIGHT, CONF_MAX_HEIGHT,
CONF_MAX_WIDTH, CONF_MAX_WIDTH,
CONF_STREAM_ADDRESS, CONF_STREAM_ADDRESS,
CONF_STREAM_COUNT,
CONF_STREAM_SOURCE, CONF_STREAM_SOURCE,
CONF_SUPPORT_AUDIO, CONF_SUPPORT_AUDIO,
CONF_VIDEO_CODEC, CONF_VIDEO_CODEC,
@ -44,11 +43,11 @@ from .const import (
DEFAULT_MAX_FPS, DEFAULT_MAX_FPS,
DEFAULT_MAX_HEIGHT, DEFAULT_MAX_HEIGHT,
DEFAULT_MAX_WIDTH, DEFAULT_MAX_WIDTH,
DEFAULT_STREAM_COUNT,
DEFAULT_SUPPORT_AUDIO, DEFAULT_SUPPORT_AUDIO,
DEFAULT_VIDEO_CODEC, DEFAULT_VIDEO_CODEC,
DEFAULT_VIDEO_MAP, DEFAULT_VIDEO_MAP,
DEFAULT_VIDEO_PACKET_SIZE, DEFAULT_VIDEO_PACKET_SIZE,
SERV_CAMERA_RTP_STREAM_MANAGEMENT,
SERV_MOTION_SENSOR, SERV_MOTION_SENSOR,
) )
from .img_util import scale_jpeg_camera_image from .img_util import scale_jpeg_camera_image
@ -121,6 +120,7 @@ CONFIG_DEFAULTS = {
CONF_VIDEO_CODEC: DEFAULT_VIDEO_CODEC, CONF_VIDEO_CODEC: DEFAULT_VIDEO_CODEC,
CONF_AUDIO_PACKET_SIZE: DEFAULT_AUDIO_PACKET_SIZE, CONF_AUDIO_PACKET_SIZE: DEFAULT_AUDIO_PACKET_SIZE,
CONF_VIDEO_PACKET_SIZE: DEFAULT_VIDEO_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): def __init__(self, hass, driver, name, entity_id, aid, config):
"""Initialize a Camera accessory object.""" """Initialize a Camera accessory object."""
self._ffmpeg = hass.data[DATA_FFMPEG] self._ffmpeg = hass.data[DATA_FFMPEG]
self._cur_session = None
for config_key in CONFIG_DEFAULTS: for config_key in CONFIG_DEFAULTS:
if config_key not in config: if config_key not in config:
config[config_key] = CONFIG_DEFAULTS[config_key] config[config_key] = CONFIG_DEFAULTS[config_key]
@ -178,6 +177,7 @@ class Camera(HomeAccessory, PyhapCamera):
"audio": audio_options, "audio": audio_options,
"address": stream_address, "address": stream_address,
"srtp": True, "srtp": True,
"stream_count": config[CONF_STREAM_COUNT],
} }
super().__init__( super().__init__(
@ -313,51 +313,42 @@ class Camera(HomeAccessory, PyhapCamera):
if not opened: if not opened:
_LOGGER.error("Failed to open ffmpeg stream") _LOGGER.error("Failed to open ffmpeg stream")
return False return False
session_info["stream"] = stream
_LOGGER.info( _LOGGER.info(
"[%s] Started stream process - PID %d", "[%s] Started stream process - PID %d",
session_info["id"], session_info["id"],
stream.process.pid, stream.process.pid,
) )
ffmpeg_watcher = async_track_time_interval( session_info["stream"] = stream
self.hass, self._async_ffmpeg_watch, FFMPEG_WATCH_INTERVAL 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.""" """Check to make sure ffmpeg is still running and cleanup if not."""
ffmpeg_pid = self._cur_session[FFMPEG_PID] ffmpeg_pid = self.sessions[session_id][FFMPEG_PID]
session_id = self._cur_session[SESSION_ID]
if pid_is_alive(ffmpeg_pid): if pid_is_alive(ffmpeg_pid):
return True return True
_LOGGER.warning("Streaming process ended unexpectedly - PID %d", ffmpeg_pid) _LOGGER.warning("Streaming process ended unexpectedly - PID %d", ffmpeg_pid)
self._async_stop_ffmpeg_watch() self._async_stop_ffmpeg_watch(session_id)
self._async_set_streaming_available(session_id) self.set_streaming_available(self.sessions[session_id]["stream_idx"])
return False return False
@callback @callback
def _async_stop_ffmpeg_watch(self): def _async_stop_ffmpeg_watch(self, session_id):
"""Cleanup a streaming session after stopping.""" """Cleanup a streaming session after stopping."""
if not self._cur_session: if FFMPEG_WATCHER not in self.sessions[session_id]:
return return
self._cur_session[FFMPEG_WATCHER]() self.sessions[session_id].pop(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()
async def stop_stream(self, session_info): async def stop_stream(self, session_info):
"""Stop the stream for the given ``session_id``.""" """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) _LOGGER.debug("No stream for session ID %s", session_id)
return return
self._async_stop_ffmpeg_watch() self._async_stop_ffmpeg_watch(session_id)
if not pid_is_alive(stream.process.pid): if not pid_is_alive(stream.process.pid):
_LOGGER.info("[%s] Stream already stopped", session_id) _LOGGER.info("[%s] Stream already stopped", session_id)

View File

@ -41,6 +41,7 @@ from .const import (
CONF_MAX_HEIGHT, CONF_MAX_HEIGHT,
CONF_MAX_WIDTH, CONF_MAX_WIDTH,
CONF_STREAM_ADDRESS, CONF_STREAM_ADDRESS,
CONF_STREAM_COUNT,
CONF_STREAM_SOURCE, CONF_STREAM_SOURCE,
CONF_SUPPORT_AUDIO, CONF_SUPPORT_AUDIO,
CONF_VIDEO_CODEC, CONF_VIDEO_CODEC,
@ -53,6 +54,7 @@ from .const import (
DEFAULT_MAX_FPS, DEFAULT_MAX_FPS,
DEFAULT_MAX_HEIGHT, DEFAULT_MAX_HEIGHT,
DEFAULT_MAX_WIDTH, DEFAULT_MAX_WIDTH,
DEFAULT_STREAM_COUNT,
DEFAULT_SUPPORT_AUDIO, DEFAULT_SUPPORT_AUDIO,
DEFAULT_VIDEO_CODEC, DEFAULT_VIDEO_CODEC,
DEFAULT_VIDEO_MAP, 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_MAX_FPS, default=DEFAULT_MAX_FPS): cv.positive_int,
vol.Optional(CONF_AUDIO_MAP, default=DEFAULT_AUDIO_MAP): cv.string, 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_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( vol.Optional(CONF_VIDEO_CODEC, default=DEFAULT_VIDEO_CODEC): vol.In(
VALID_VIDEO_CODECS VALID_VIDEO_CODECS
), ),

View File

@ -18,7 +18,7 @@ Adafruit-SHT31==1.0.2
# Adafruit_BBIO==1.1.1 # Adafruit_BBIO==1.1.1
# homeassistant.components.homekit # homeassistant.components.homekit
HAP-python==2.9.2 HAP-python==3.0.0
# homeassistant.components.mastodon # homeassistant.components.mastodon
Mastodon.py==1.5.1 Mastodon.py==1.5.1

View File

@ -5,7 +5,7 @@
-r requirements_test.txt -r requirements_test.txt
# homeassistant.components.homekit # homeassistant.components.homekit
HAP-python==2.9.2 HAP-python==3.0.0
# homeassistant.components.plugwise # homeassistant.components.plugwise
Plugwise_Smile==1.1.0 Plugwise_Smile==1.1.0