You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

166 lines
6.8 KiB
Python

from __future__ import annotations
import asyncio
import base64
import logging
import time
from typing import List, Tuple
import numpy as np
import cv2
import websockets
from config import CPP_WS_URI, CPP_CTRL_WS_URI, DROP_DECODE_WHEN_BUSY, PUBLISH_RAW_BEFORE_INFER, PREVIEW_TARGET_W
from utils import json_loads, json_dumps
from state import detected_cameras, video_clients, ptz_states, detection_queue
from media import publish_preview, maybe_downscale
from detection import batcher
logger = logging.getLogger("PTZTracker")
async def cpp_ws_loop() -> None:
frames_total = 0
skipped_total = 0
last_log = time.time()
while True:
try:
logger.info("[CPP WS] connecting to %s ...", CPP_WS_URI)
async with websockets.connect(
CPP_WS_URI, max_size=None, compression=None, ping_interval=None, close_timeout=1.0
) as ws:
logger.info("[CPP WS] connected")
async for raw in ws:
if isinstance(raw, (bytes, bytearray)):
buf = memoryview(raw)
if len(buf) < 3 or buf[0] != 1:
continue
idx = int.from_bytes(buf[1:3], "big")
from config import CAMERA_CONFIG
if idx not in CAMERA_CONFIG:
continue
if DROP_DECODE_WHEN_BUSY and ptz_states[idx]["proc_busy"] and (not video_clients):
skipped_total += 1
continue
arr = np.frombuffer(buf[3:], dtype=np.uint8)
img = cv2.imdecode(arr, cv2.IMREAD_COLOR)
if img is None:
continue
else:
try:
msg = json_loads(raw)
except Exception:
continue
if msg.get("type") != "image":
continue
idx_str, b64 = msg["data"].split("|", 1)
idx = int(idx_str)
from config import CAMERA_CONFIG
if idx not in CAMERA_CONFIG:
continue
if DROP_DECODE_WHEN_BUSY and ptz_states[idx]["proc_busy"] and (not video_clients):
skipped_total += 1
continue
arr = np.frombuffer(base64.b64decode(b64), dtype=np.uint8)
img = cv2.imdecode(arr, cv2.IMREAD_COLOR)
if img is None:
continue
frames_total += 1
h, w = img.shape[:2]
now = time.time()
if now - last_log >= 1.0:
logger.info("[CPP WS] recv: %d fps, skipped=%d | last cam=%s | %dx%d | clients=%d",
frames_total, skipped_total, idx, w, h, len(video_clients))
frames_total = 0; skipped_total = 0; last_log = now
detected_cameras.add(idx)
if PUBLISH_RAW_BEFORE_INFER and video_clients:
down, _, _ = maybe_downscale(img, PREVIEW_TARGET_W)
publish_preview(idx, down)
pst = ptz_states[idx]
if pst["proc_busy"]:
continue
pst["proc_busy"] = True
async def process_frame(cam_idx: int, frame):
try:
assert batcher is not None, "batcher is not initialized"
await batcher.submit(cam_idx, frame, cam_idx in from_state_ptz_ids())
except Exception as exc:
logger.error("Error processing frame for camera %s: %s", cam_idx, exc)
finally:
ptz_states[cam_idx]["proc_busy"] = False
# helper to avoid circular import at top-level
def from_state_ptz_ids():
from state import PTZ_CAM_IDS
return PTZ_CAM_IDS
asyncio.create_task(process_frame(idx, img))
except Exception as exc:
logger.error("[CPP WS] error: %s", exc)
await asyncio.sleep(0.5)
async def cpp_detection_loop() -> None:
assert detection_queue is not None
RESYNC_EVERY_SEC = 10.0
def _snapshot_states():
from config import CAMERA_CONFIG
snap = []
for cid in CAMERA_CONFIG.keys():
st = ptz_states.get(cid, {})
snap.append({"IdCamera": int(cid), "detection": bool(st.get("rec_active", False))})
return snap
while True:
try:
logger.info("[CPP CTRL] connecting to %s ...", CPP_CTRL_WS_URI)
async with websockets.connect(
CPP_CTRL_WS_URI, max_size=None, compression=None, ping_interval=None, close_timeout=1.0
) as ws:
logger.info("[CPP CTRL] connected")
try:
for item in _snapshot_states():
await ws.send(json_dumps({"type": "detection", "data": item}))
except Exception as e:
logger.error("[CPP CTRL] initial resync failed: %s", e)
raise
last_resync = time.time()
while True:
now = time.time()
if now - last_resync >= RESYNC_EVERY_SEC and detection_queue.empty():
try:
for item in _snapshot_states():
await ws.send(json_dumps({"type": "detection", "data": item}))
except Exception as e:
logger.error("[CPP CTRL] periodic resync failed: %s", e)
break
last_resync = now
try:
payload = await asyncio.wait_for(detection_queue.get(), timeout=1.0)
except asyncio.TimeoutError:
continue
try:
await ws.send(json_dumps(payload))
last_resync = time.time()
except Exception as exc:
logger.error("[CPP CTRL] send failed: %s", exc)
try:
detection_queue.put_nowait(payload)
except Exception:
pass
break
finally:
detection_queue.task_done()
except Exception as exc:
logger.error("[CPP CTRL] error: %s", exc)
await asyncio.sleep(1.0)