From b09dd2fcdccf981b6435e3ef46fae36fb1fcd49f Mon Sep 17 00:00:00 2001 From: Brett Jia Date: Wed, 11 Jan 2023 22:12:30 -0500 Subject: [PATCH] aiortc logging, detect ffmpeg input source format --- plugins/arlo/src/arlo_plugin/camera.py | 8 +--- plugins/arlo/src/arlo_plugin/logging.py | 45 ++++++++++++++++++- plugins/arlo/src/arlo_plugin/provider.py | 3 +- .../arlo/src/arlo_plugin/rtcpeerconnection.py | 31 ++++++++++--- 4 files changed, 71 insertions(+), 16 deletions(-) diff --git a/plugins/arlo/src/arlo_plugin/camera.py b/plugins/arlo/src/arlo_plugin/camera.py index 18de02d50..3eaaba8b7 100644 --- a/plugins/arlo/src/arlo_plugin/camera.py +++ b/plugins/arlo/src/arlo_plugin/camera.py @@ -154,7 +154,6 @@ class ArloCamera(ScryptedDeviceBase, Camera, VideoCamera, Intercom, MotionSensor ffmpeg_params = json.loads(await scrypted_sdk.mediaManager.convertMediaObjectToBuffer(media, ScryptedMimeTypes.FFmpegInput.value)) self.logger.debug(f"Received ffmpeg params: {ffmpeg_params}") - endpoint = ffmpeg_params.get("url") options = {} current_key = None for arg in ffmpeg_params["inputArguments"]: @@ -166,14 +165,9 @@ class ArloCamera(ScryptedDeviceBase, Camera, VideoCamera, Intercom, MotionSensor options[current_key] = "" continue options[current_key] = (options[current_key] + " " + arg).strip() - if current_key == "i": - endpoint = options[current_key] self.logger.debug(f"Parsed ffmpeg params: {options}") - if endpoint is None: - raise Exception("Malformed ffmpeg arguments, input endpoint not provided") - session_id, ice_servers = self.provider.arlo.StartPushToTalk(self.arlo_basestation, self.arlo_device) self.logger.debug(f"Received ice servers: {[ice['url'] for ice in ice_servers]}") @@ -201,7 +195,7 @@ class ArloCamera(ScryptedDeviceBase, Camera, VideoCamera, Intercom, MotionSensor pc = self.pc = BackgroundRTCPeerConnection() self.sdp_answered = False - pc.add_rtsp_audio(endpoint, options) + pc.add_audio(options) offer = await pc.createOffer() self.logger.info(f"Arlo offer sdp:\n{offer.sdp}") diff --git a/plugins/arlo/src/arlo_plugin/logging.py b/plugins/arlo/src/arlo_plugin/logging.py index 76b5aebe3..785b0f207 100644 --- a/plugins/arlo/src/arlo_plugin/logging.py +++ b/plugins/arlo/src/arlo_plugin/logging.py @@ -1,4 +1,5 @@ import logging +import sys class ScryptedDeviceLoggingWrapper(logging.Handler): @@ -40,4 +41,46 @@ class ScryptedDeviceLoggerMixin: def logger(self): if self._logger is None: self._logger = createScryptedLogger(self, self.logger_name) - return self._logger \ No newline at end of file + return self._logger + +aiortc_loggers = [ + "aiortc", + "aiortc.rtcdatachannel", + "aiortc.rtcdtlstransport", + "aiortc.rtcicetransport", + "aiortc.rtcpeerconnection", + "aiortc.rtcrtpreceiver", + "aiortc.rtcrtpsender", + "aiortc.rtcrtptransceiver", + "aiortc.rtcsctptransport", + "aiortc.codecs.h264", + "aiortc.contrib.media", + "aiortc.contrib.signaling", +] + +def init_aiortc_logger(logger_name): + # get logger instance used by aiortc + logger = logging.getLogger(logger_name) + logger.setLevel(logging.INFO) + + # output logger to stdout + ch = logging.StreamHandler(sys.stdout) + + # log formatting + fmt = logging.Formatter("(arlo) %(levelname)s:%(name)s:%(asctime)s.%(msecs)03d %(message)s", "%H:%M:%S") + ch.setFormatter(fmt) + + # configure handler to logger + logger.addHandler(ch) + + if logger_name == "aiortc.rtcrtpsender": + # rtcrtpsender is extremely noisy for DEBUG, so filter out all + # the packet logs + logger.addFilter(lambda record: 0 if ") > " in record.getMessage() else 1) + +for log in aiortc_loggers: + init_aiortc_logger(log) + +def propagate_aiortc_logging_level(log_level): + for log in aiortc_loggers: + logging.getLogger(log).setLevel(log_level) \ No newline at end of file diff --git a/plugins/arlo/src/arlo_plugin/provider.py b/plugins/arlo/src/arlo_plugin/provider.py index a765bdb55..0a64dde29 100644 --- a/plugins/arlo/src/arlo_plugin/provider.py +++ b/plugins/arlo/src/arlo_plugin/provider.py @@ -12,7 +12,7 @@ from .arlo.arlo_async import change_stream_class from .arlo.logging import logger as arlo_lib_logger from .camera import ArloCamera from .doorbell import ArloDoorbell -from .logging import ScryptedDeviceLoggerMixin +from .logging import ScryptedDeviceLoggerMixin, propagate_aiortc_logging_level from .util import BackgroundTaskMixin from .rtcpeerconnection import logger as background_rtc_logger @@ -161,6 +161,7 @@ class ArloProvider(ScryptedDeviceBase, Settings, DeviceProvider, DeviceDiscovery device.logger.setLevel(log_level) arlo_lib_logger.setLevel(log_level) background_rtc_logger.setLevel(log_level) + propagate_aiortc_logging_level(log_level) def propagate_transport(self): self.print(f"Setting plugin transport to {self.arlo_transport}") diff --git a/plugins/arlo/src/arlo_plugin/rtcpeerconnection.py b/plugins/arlo/src/arlo_plugin/rtcpeerconnection.py index 2c2a59ba3..c62b0e666 100644 --- a/plugins/arlo/src/arlo_plugin/rtcpeerconnection.py +++ b/plugins/arlo/src/arlo_plugin/rtcpeerconnection.py @@ -1,7 +1,6 @@ from aiortc import RTCPeerConnection from aiortc.contrib.media import MediaPlayer import asyncio -import inspect import threading import logging import queue @@ -108,17 +107,35 @@ class BackgroundRTCPeerConnection: async def close(self): await self.__run_background(self.pc.close(), await_result=False, stop_loop=True) - def add_rtsp_audio(self, endpoint, options): - """Adds an audio track to the RTCPeerConnection given a source RTSP url. + def add_audio(self, options): + """Adds an audio track to the RTCPeerConnection given FFmpeg options. This constructs a MediaPlayer in the background thread's asyncio loop, since MediaPlayer also utilizes coroutines and asyncio. - Note that this may block the background thread's event loop if the RTSP + Note that this may block the background thread's event loop if the server is not yet ready. """ - def add_rtsp_audio_background(): - media_player = MediaPlayer(endpoint, options=options) + try: + input = options["i"] + format = options.get("f") + if format is None and input.startswith("rtsp"): + format = "rtsp" + except: + logger.error("error detecting what input file and format to use") + raise + + def add_audio_background(): + media_player = MediaPlayer(input, format=format, options=options) + + # patch the player's stop function to close RTC if + # the media ends before RTC is closed + old_stop = media_player._stop + def new_stop(*args, **kwargs): + old_stop(*args, **kwargs) + self.main_loop.call_soon_threadsafe(self.main_loop.create_task, self.close()) + media_player._stop = new_stop + self.pc.addTrack(media_player.audio) - self.background_loop.call_soon_threadsafe(add_rtsp_audio_background) \ No newline at end of file + self.background_loop.call_soon_threadsafe(add_audio_background) \ No newline at end of file