"""Bildformate des Wi-Fi-Display-Standards und die Auswahl daraus. Der Fernseher meldet in einer Bitmaske, welche Auflösungen er beherrscht. Wir suchen die beste heraus, die beide Seiten können, und melden genau diese eine zurück. """ from dataclasses import dataclass from typing import List, Optional @dataclass(frozen=True) class VideoFormat: cea_bit: int width: int height: int fps: int def __str__(self) -> str: return f"{self.width}x{self.height}@{self.fps}" # CEA-Tabelle des Standards; die Position entspricht dem Bit in der Maske. CEA = [ VideoFormat(0, 640, 480, 60), VideoFormat(1, 720, 480, 60), VideoFormat(2, 720, 480, 60), # interlaced VideoFormat(3, 720, 576, 50), VideoFormat(4, 720, 576, 50), # interlaced VideoFormat(5, 1280, 720, 30), VideoFormat(6, 1280, 720, 60), VideoFormat(7, 1920, 1080, 30), VideoFormat(8, 1920, 1080, 60), VideoFormat(9, 1920, 1080, 60), # interlaced VideoFormat(10, 1280, 720, 25), VideoFormat(11, 1280, 720, 50), VideoFormat(12, 1920, 1080, 25), VideoFormat(13, 1920, 1080, 50), VideoFormat(14, 1920, 1080, 50), # interlaced VideoFormat(15, 1280, 720, 24), VideoFormat(16, 1920, 1080, 24), ] INTERLACED = {2, 4, 9, 14} # Wunschreihenfolge: flüssig und scharf, ohne zu übertreiben. PREFERENCE = [5, 7, 6, 11, 13, 10, 12, 15, 16, 8, 0, 1, 3] @dataclass(frozen=True) class SinkCapability: profile: int level: int cea_mask: int vesa_mask: int hh_mask: int def parse_video_formats(value: str) -> List[SinkCapability]: """Zerlegt den Wert von wfd_video_formats. Aufbau: native, preferred-display-mode, dann je Codec elf Felder (profile, level, CEA, VESA, HH, latency, min-slice, slice-enc, frame-rate-control, max-hres, max-vres). """ parts = value.strip().split() if len(parts) < 3: return [] result = [] i = 2 # native und preferred-display-mode überspringen while i + 4 < len(parts): try: capability = SinkCapability( profile=int(parts[i], 16), level=int(parts[i + 1], 16), cea_mask=int(parts[i + 2], 16), vesa_mask=int(parts[i + 3], 16), hh_mask=int(parts[i + 4], 16), ) except ValueError: break result.append(capability) i += 11 return result def choose(capabilities: List[SinkCapability], max_height: int = 1080) -> Optional[VideoFormat]: """Bestes gemeinsames Format, oder None wenn es keines gibt.""" mask = 0 for capability in capabilities: mask |= capability.cea_mask for bit in PREFERENCE: if bit in INTERLACED or not (mask & (1 << bit)): continue candidate = CEA[bit] if candidate.height > max_height: continue return candidate return None def build_selection(fmt: VideoFormat, profile: int, level: int) -> str: """Antwortwert mit genau einem gesetzten Bit - damit der Fernseher weiß, was tatsächlich kommt.""" cea = 1 << fmt.cea_bit return ( f"{0:02X} {0:02X} {profile:02X} {level:02X} " f"{cea:08X} {0:08X} {0:08X} {0:02X} {0:04X} {0:04X} {0:02X} none none" ) def parse_rtp_port(value: Optional[str]) -> int: """Liest aus wfd_client_rtp_ports den Port, auf dem der Fernseher lauscht.""" if not value: return 0 for part in value.strip().split(): if part.isdigit(): port = int(part) if 1 <= port <= 65535: return port return 0