diff options
author | Devaev Maxim <[email protected]> | 2021-01-26 06:28:30 +0300 |
---|---|---|
committer | Devaev Maxim <[email protected]> | 2021-01-26 06:28:40 +0300 |
commit | 4c32ce01ad97e9b8b8b9ceb298f93ffd065e0ece (patch) | |
tree | 870d8a054ccd84f8380f18bb78b5e29ee68e08dd /kvmd | |
parent | a0ae387a6c24daec8ccef208a724035914d6c428 (diff) |
many fixes for h264 chain
Diffstat (limited to 'kvmd')
-rw-r--r-- | kvmd/apps/vnc/__init__.py | 22 | ||||
-rw-r--r-- | kvmd/apps/vnc/server.py | 43 | ||||
-rw-r--r-- | kvmd/clients/streamer.py | 42 |
3 files changed, 81 insertions, 26 deletions
diff --git a/kvmd/apps/vnc/__init__.py b/kvmd/apps/vnc/__init__.py index 07be2abd..7aeb283e 100644 --- a/kvmd/apps/vnc/__init__.py +++ b/kvmd/apps/vnc/__init__.py @@ -24,8 +24,10 @@ from typing import List from typing import Optional from ...clients.kvmd import KvmdClient -from ...clients.streamer import StreamerHttpClient -from ...clients.streamer import StreamerMemsinkClient +from ...clients.streamer import StreamFormats +from ...clients.streamer import BaseStreamerClient +from ...clients.streamer import HttpStreamerClient +from ...clients.streamer import MemsinkStreamerClient from ... import htclient @@ -46,11 +48,17 @@ def main(argv: Optional[List[str]]=None) -> None: user_agent = htclient.make_user_agent("KVMD-VNC") - def make_memsink(name: str) -> Optional[StreamerMemsinkClient]: + def make_memsink_streamer(name: str, fmt: int) -> Optional[MemsinkStreamerClient]: if getattr(config.memsink, name).sink: - return StreamerMemsinkClient(name=name, **getattr(config.memsink, name)._unpack()) + return MemsinkStreamerClient(name.upper(), fmt, **getattr(config.memsink, name)._unpack()) return None + streamers: List[BaseStreamerClient] = list(filter(None, [ + make_memsink_streamer("h264", StreamFormats.H264), + make_memsink_streamer("jpeg", StreamFormats.JPEG), + HttpStreamerClient(name="JPEG", user_agent=user_agent, **config.streamer._unpack()), + ])) + VncServer( host=config.server.host, port=config.server.port, @@ -65,11 +73,7 @@ def main(argv: Optional[List[str]]=None) -> None: keymap_path=config.keymap, kvmd=KvmdClient(user_agent=user_agent, **config.kvmd._unpack()), - streamers=list(filter(None, [ - make_memsink("h264"), - make_memsink("jpeg"), - StreamerHttpClient(name="jpeg", user_agent=user_agent, **config.streamer._unpack()), - ])), + streamers=streamers, vnc_auth_manager=VncAuthManager(**config.auth.vncauth._unpack()), **config.server.keepalive._unpack(), diff --git a/kvmd/apps/vnc/server.py b/kvmd/apps/vnc/server.py index dbb82d5e..769d9002 100644 --- a/kvmd/apps/vnc/server.py +++ b/kvmd/apps/vnc/server.py @@ -48,6 +48,7 @@ from ...clients.kvmd import KvmdClient from ...clients.streamer import StreamerError from ...clients.streamer import StreamerPermError +from ...clients.streamer import StreamFormats from ...clients.streamer import BaseStreamerClient from ... import tools @@ -154,7 +155,7 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes try: await asyncio.wait_for(self.__stage2_encodings_accepted, timeout=5) except asyncio.TimeoutError: - raise RfbError("No SetEncodings message recieved from the clienta in 5 secs") + raise RfbError("No SetEncodings message recieved from the client in 5 secs") assert self.__kvmd_session try: @@ -191,28 +192,45 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes async def __streamer_task_loop(self) -> None: logger = get_logger(0) await self.__stage3_ws_connected - streamer = self.__streamers[0] + streamer = self.__get_preferred_streamer() while True: try: streaming = False - async for (online, width, height, data, h264) in streamer.read_stream(): + async for (online, width, height, data) in streamer.read_stream(): if not streaming: - logger.info("[streamer] %s: Streaming from %s ...", self._remote, streamer) + logger.info("[streamer] %s: Streaming ...", self._remote) streaming = True if online: - await self.__send_fb_real(width, height, data, h264) + await self.__send_fb_real(width, height, data, streamer.get_format()) else: await self.__send_fb_stub("No signal") except StreamerError as err: if isinstance(err, StreamerPermError): - streamer = self.__streamers[-1] + streamer = self.__get_default_streamer() logger.info("[streamer] %s: Permanent error: %s; switching to %s ...", self._remote, err, streamer) else: logger.info("[streamer] %s: Waiting for stream: %s", self._remote, err) await self.__send_fb_stub("Waiting for stream ...") await asyncio.sleep(1) - async def __send_fb_real(self, width: int, height: int, data: bytes, h264: bool) -> None: + def __get_preferred_streamer(self) -> BaseStreamerClient: + formats = { + StreamFormats.JPEG: "has_tight", + StreamFormats.H264: "has_h264", + } + streamer: Optional[BaseStreamerClient] = None + for streamer in self.__streamers: + if getattr(self._encodings, formats[streamer.get_format()]): + get_logger(0).info("[streamer] %s: Using preferred %s", self._remote, streamer) + return streamer + raise RuntimeError("No streamers found") + + def __get_default_streamer(self) -> BaseStreamerClient: + streamer = self.__streamers[-1] + get_logger(0).info("[streamer] %s: Using default %s", self._remote, streamer) + return streamer + + async def __send_fb_real(self, width: int, height: int, data: bytes, fmt: int) -> None: async with self.__lock: if self.__fb_requested: if self._width != width or self._height != height: @@ -223,7 +241,16 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes await self.__send_fb_stub(msg, no_lock=True) return await self._send_resize(width, height) - await (self._send_fb_h264 if h264 else self._send_fb_jpeg)(data) + + if fmt == StreamFormats.JPEG: + await self._send_fb_jpeg(data) + elif fmt == StreamFormats.H264: + if not self._encodings.has_h264: + raise StreamerPermError("The client doesn't want to accept H264 anymore") + await self._send_fb_h264(data) + else: + raise RuntimeError(f"Unknown format: {fmt}") + self.__fb_stub = None self.__fb_requested = False diff --git a/kvmd/clients/streamer.py b/kvmd/clients/streamer.py index 7d572026..a0a07bf5 100644 --- a/kvmd/clients/streamer.py +++ b/kvmd/clients/streamer.py @@ -52,14 +52,23 @@ class StreamerPermError(StreamerError): # ===== +class StreamFormats: + JPEG = 1195724874 # V4L2_PIX_FMT_JPEG + H264 = 875967048 # V4L2_PIX_FMT_H264 + _MJPEG = 1196444237 # V4L2_PIX_FMT_MJPEG + + class BaseStreamerClient: - async def read_stream(self) -> AsyncGenerator[Tuple[bool, int, int, bytes, bool], None]: + def get_format(self) -> int: + raise NotImplementedError() + + async def read_stream(self) -> AsyncGenerator[Tuple[bool, int, int, bytes], None]: if self is not None: # XXX: Vulture and pylint hack raise NotImplementedError() yield -class StreamerHttpClient(BaseStreamerClient): +class HttpStreamerClient(BaseStreamerClient): def __init__( self, name: str, @@ -78,7 +87,10 @@ class StreamerHttpClient(BaseStreamerClient): self.__timeout = timeout self.__user_agent = user_agent - async def read_stream(self) -> AsyncGenerator[Tuple[bool, int, int, bytes, bool], None]: + def get_format(self) -> int: + return StreamFormats.JPEG + + async def read_stream(self) -> AsyncGenerator[Tuple[bool, int, int, bytes], None]: try: async with self.__make_http_session() as session: async with session.get( @@ -103,7 +115,6 @@ class StreamerHttpClient(BaseStreamerClient): int(frame.headers["X-UStreamer-Width"]), int(frame.headers["X-UStreamer-Height"]), data, - False, ) except Exception as err: # Тут бывают и ассерты, и KeyError, и прочая херня if isinstance(err, StreamerTempError): @@ -141,13 +152,14 @@ class StreamerHttpClient(BaseStreamerClient): reader.read = types.MethodType(read, reader) # type: ignore def __str__(self) -> str: - return f"StreamerHttpClient({self.__name})" + return f"HttpStreamerClient({self.__name})" -class StreamerMemsinkClient(BaseStreamerClient): +class MemsinkStreamerClient(BaseStreamerClient): def __init__( self, name: str, + fmt: int, obj: str, lock_timeout: float, wait_timeout: float, @@ -155,6 +167,7 @@ class StreamerMemsinkClient(BaseStreamerClient): ) -> None: self.__name = name + self.__fmt = fmt self.__kwargs: Dict = { "obj": obj, "lock_timeout": lock_timeout, @@ -162,7 +175,10 @@ class StreamerMemsinkClient(BaseStreamerClient): "drop_same_frames": drop_same_frames, } - async def read_stream(self) -> AsyncGenerator[Tuple[bool, int, int, bytes, bool], None]: + def get_format(self) -> int: + return self.__fmt + + async def read_stream(self) -> AsyncGenerator[Tuple[bool, int, int, bytes], None]: if ustreamer is None: raise StreamerPermError("Missing ustreamer library") try: @@ -170,17 +186,25 @@ class StreamerMemsinkClient(BaseStreamerClient): while True: frame = await aiotools.run_async(sink.wait_frame) if frame is not None: + self.__check_format(frame["format"]) yield ( frame["online"], frame["width"], frame["height"], frame["data"], - (frame["format"] == 875967048), # V4L2_PIX_FMT_H264 ) + except StreamerPermError: + raise except FileNotFoundError as err: raise StreamerTempError(tools.efmt(err)) except Exception as err: raise StreamerPermError(tools.efmt(err)) + def __check_format(self, fmt: int) -> None: + if fmt == StreamFormats._MJPEG: # pylint: disable=protected-access + fmt = StreamFormats.JPEG + if fmt != self.__fmt: + raise StreamerPermError("Invalid sink format") + def __str__(self) -> str: - return f"StreamerMemsinkClient({self.__name})" + return f"MemsinkStreamerClient({self.__name})" |