From d8674e599d30c004dc90dbca0ecf41a100615064 Mon Sep 17 00:00:00 2001 From: gx Date: Wed, 20 May 2026 23:15:13 +0100 Subject: [PATCH] =?UTF-8?q?controller:=20Phase=205d=20=E2=80=94=20audio=5F?= =?UTF-8?q?zmq=5Fendpoint=20=D0=B4=D0=BB=D1=8F=20split-process=20architect?= =?UTF-8?q?ure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InstanceCfg.audio_zmq_endpoint (optional) — ZMQ адрес отдельного audio sidecar ffmpeg. dispatcher._audio_set + _intercom_set теперь используют _audio_client(inst) — separate ZMQ socket к sidecar. Fallback: если audio_zmq_endpoint=null → реверт к video pipeline zmq (Phase 5a single-source behaviour). Зачем: multi-source audio chain в одном pipeline с video блокирует video pacing (см. feedback_audio-chain-blocks-video). Split разделяет A/V на 2 ffmpeg процесса; audio sidecar publish'нет к mediamtx, video pipeline consume'нет audio как RTSP source + remux к combined output. Co-Authored-By: Claude Opus 4.7 --- controller/cuda_grid_controller/config.py | 7 ++++++- controller/cuda_grid_controller/dispatch.py | 20 ++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/controller/cuda_grid_controller/config.py b/controller/cuda_grid_controller/config.py index f53d5d2..8dfd366 100644 --- a/controller/cuda_grid_controller/config.py +++ b/controller/cuda_grid_controller/config.py @@ -57,7 +57,12 @@ class InstanceCfg(BaseModel): name: str = Field(description="уникальное имя — становится частью HA entity ID") zmq_endpoint: str = Field( - description="ZMQ endpoint FFmpeg's zmq filter (tcp://host:port)" + description="ZMQ endpoint видео-pipeline (cuda_grid + overlay filters)" + ) + audio_zmq_endpoint: str | None = Field( + default=None, + description="ZMQ endpoint отдельного audio sidecar (Phase 5d split-process). " + "None = audio_set/intercom вызывают video pipeline (Phase 5a single source)", ) default_layout: str = "quad" filter_target: str = Field( diff --git a/controller/cuda_grid_controller/dispatch.py b/controller/cuda_grid_controller/dispatch.py index 47c0648..aa36366 100644 --- a/controller/cuda_grid_controller/dispatch.py +++ b/controller/cuda_grid_controller/dispatch.py @@ -109,6 +109,17 @@ class CommandDispatcher: self._zmq_clients[inst.name] = c return c + def _audio_client(self, inst: InstanceCfg) -> FFmpegZmqClient: + """ZMQ client к audio sidecar (Phase 5d). Fallback к video pipeline + если split-process не configured.""" + endpoint = inst.audio_zmq_endpoint or inst.zmq_endpoint + key = f"{inst.name}:audio" + c = self._zmq_clients.get(key) + if c is None: + c = FFmpegZmqClient(endpoint) + self._zmq_clients[key] = c + return c + def _find_instance(self, name: str) -> InstanceCfg | None: return next((i for i in self.cfg.instances if i.name == name), None) @@ -141,7 +152,8 @@ class CommandDispatcher: # ─── Audio ───────────────────────────────────────────────────── async def _audio_set(self, inst: InstanceCfg, source_name: str) -> None: - """Switch audio к source_name через ZMQ команду astreamselect map .""" + """Switch audio к source_name через ZMQ команду astreamselect map . + Phase 5d: команда идёт в audio sidecar (отдельный ffmpeg).""" if not inst.audio_sources: log.warning("dispatch.no_audio_sources", instance=inst.name) return @@ -152,7 +164,7 @@ class CommandDispatcher: available=[s.name for s in inst.audio_sources]) return - client = self._client(inst) + client = self._audio_client(inst) try: reply = await client.send_command( inst.audio_filter_target, "map", str(src.index) @@ -183,8 +195,8 @@ class CommandDispatcher: async def _intercom_set(self, inst: InstanceCfg, active: bool) -> None: """Ducking pattern: при intercom ON → music volume ↓ + intercom ↑. - После end — restore. Volume commands отправляются параллельно.""" - client = self._client(inst) + После end — restore. Phase 5d: команды идут в audio sidecar.""" + client = self._audio_client(inst) music_vol = inst.music_ducked_volume if active else 1.0 intercom_vol = 1.0 if active else 0.0