You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Musubi/docs/04-fahrplan.md

9.9 KiB

Fahrplan

Jeder Meilenstein endet mit einem Abnahmetest. Erst wenn er bestanden ist, beginnt der nächste. Termine sind offen.

Absprache 2026-10-21: Die Reihenfolge darf nach Absprache angepasst werden. Langzeittests (z. B. M1: 4 h) laufen gesammelt am Wochenende mit einem getaggten Build (m1-candidate); die Entwicklung von M2 beginnt vorher.

Meilenstein Ohne neue Hardware testbar?
M0 Grundgerüst ja
M1 Bild ja (NDI Tools / OBS als Quelle)
M2 Ton-Matrix ja (MOTU M2 vorhanden)
M3 Szenen und Fernsteuerung ja (MacBook + Mac mini)
M4 Intercom Stufe 1: Hören + PGM-Vorschau ja (Heimnetz)
M5 Intercom Stufe 2: Sprechen ja (Heimnetz, HTTPS)
M6 Tally ja (ATEM vorhanden)
M7 Generalprobe nein – braucht ZowieBoxen, Switches, Dante, Ui12

M0 – Grundgerüst

  • Xcode-Projekt „Musubi“, macOS 14, Universal Build (arm64 + x86_64), Bundle-ID studio.zielke.musubi
  • Modulstruktur laut 02-architektur.md
  • NDI SDK eingebunden (Modulemap), Version wird beim Start geloggt
  • design/Theme.swift übernommen, leeres Hauptfenster mit Quellenliste
  • Swift-Testing-Target mit erstem Test für MusubiCore
  • Abnahme: Release-Build startet auf MacBook und Mac mini 2018; die Quellenliste zeigt eine NDI-Testquelle aus dem Netz.

M1 – Bild

  • NDI-Empfang einer Quelle (HX3 und volles NDI) – volles NDI ✓, HX3 offen bis ZowieBoxen da sind
  • Vollbild auf gewähltem Bildschirm, ohne Menüleiste, Mauszeiger ausgeblendet
  • Anzeigemodus 50 Hz setzen, wenn verfügbar
  • Zwei Quellen gleichzeitig auf zwei Bildschirmen (Mac mini: PGM + Multiview)
  • Ruhezustand blockieren, Auto-Reconnect
  • Abnahme: 4 Stunden, 2 Quellen, 2 Bildschirme auf dem Mac mini 2018 – keine sichtbaren Aussetzer, CPU-Last notiert, verlorene Frames < 0,1 %.

M2 – Ton-Matrix

  • NDI-Ton einer Quelle auf beliebige Ausgangskanäle (z. B. MOTU M2 Out 2)
  • Reiner Audio-Empfang (ohne Video-Bandbreite)
  • Mikrofon-Eingang (Headset) auf Kanal (Out 1), Sprechtaste (Tastenkürzel, gedrückt halten / umschalten)
  • Pegelanzeige, Gain, Mute je Route
  • Drift-Ausgleich zwischen NDI-Takt und Interface-Takt
  • Abnahme: 4 Stunden ohne Knacksen und ohne hörbaren Versatz; Sprechtaste reagiert ohne Verzögerung.

M3 – Szenen und Fernsteuerung

  • Szenen speichern, laden, exportieren (JSON)
  • Rolle Regie/Knoten; Knoten per Bonjour finden
  • Regie setzt Szene am Knoten; Knoten meldet Status zurück
  • Beim Start letzte Szene laden
  • Stand 2026-10-21: Abnahme bestanden – Neustart beider Macs, „OP-Live“ stellt sich ohne Eingriff wieder her (von Samuel bestätigt).
  • Abnahme: Beide Macs neu starten – die Szene „OP-Live“ stellt sich ohne Eingriff wieder her.

M4 – Intercom Stufe 1

  • Web-Server nur auf der Klinik-Schnittstelle
  • QR-Code mit Adresse in der App
  • Handy-Seite: Name/Rolle, Talkback hören, PGM-Vorschau (WebRTC)
  • Stand 2026-10-22: umgesetzt, mit Chrome (headless) am Mac geprüft; Abnahme mit iPhone und Android offen.
  • Abnahme: iPhone und Android im Heimnetz, Verzögerung der Vorschau gemessen < 0,5 s.

M5 – Intercom Stufe 2

  • HTTPS mit eigener Stammzertifizierung (Installationsanleitung fürs Handy)
  • Sprechtaste auf dem Handy → Ton-Matrix
  • Optional: TURN-Relay als Plan B konfigurierbar (06-netzwerk.md)
  • Abnahme: Gegensprechen iPhone ↔ Regie und Android ↔ Regie im selben Durchgang.

M6 – Tally

  • ATEM-Status (PGM/Vorschau) lesen
  • Kamera-Eingang einer Handy-Rolle zuordnen
  • Handy-Rand rot (PGM) / grün (Vorschau)
  • Abnahme: Schnitt am Micro Panel färbt das richtige Handy in unter 200 ms.

M7 – Generalprobe

  • Kompletter Aufbau laut 05-testplan-hardware.md
  • Abnahme: 3 Stunden durchgehender Betrieb mit allen Geräten; OBS + DistroAV bleibt als Notlösung auf beiden Macs installiert.

Erkenntnisse

(nach jedem Meilenstein ergänzen)

M0 – Grundgerüst (bestanden 2026-10-21)

  • Abnahme: Release-Build auf Mac Studio (Apple Silicon, Entwicklungsrechner) und Mac mini 2018 (i5-8500B, macOS 15.7.7) gestartet; beide zeigen „NDI Test Patterns“ in der Quellenliste. Das MacBook nutzt denselben arm64-Teil und wurde nicht separat geprüft.
  • Xcode 27 baut weiterhin x86_64 für macOS 14.
  • NDI SDK 6.3.2: libndi.dylib ist universal und hat @rpath/libndi.dylib als Install-Name; Einbetten nach Contents/Frameworks reicht.
  • Hardened Runtime + Ad-hoc-Signatur → dyld lehnt die eingebettete libndi.dylib ab („different Team IDs“). Vorerst aus; mit echter Team-ID für die Notarisierung wieder einschalten.
  • Mac mini im Leerlauf mit Quellensuche: ca. 2 % CPU, ca. 52 MB RAM.
  • Mac mini ist per SSH erreichbar (Entfernte Anmeldung + Bildschirmfreigabe): App per scp verteilen, Quarantäne entfernen, Logs mit log stream mitlesen.

M1 – Bild (umgesetzt 2026-10-21, Abnahme offen)

  • „NDI Test Patterns“ sendet ein Standbild nur einmal. Eine gehaltene NDI-Verbindung zählt deshalb als lebendig; neu verbunden wird nur, wenn Frames und Verbindung fehlen.
  • Volles NDI 1080p50 mit bewegtem Bild: ~128 Mbit/s, ~50 % eines Kerns auf dem Mac mini – fast nur NDI-Empfang/Dekodierung. HX3 (ZowieBox) noch zu messen.
  • Verlorene Frames können vom Sender kommen: OBS auf dem Mac Studio ließ Frames aus, während dort Release-Builds liefen (~335 Frames in 6 min). Für Langzeittests darf auf dem Sender nichts Schweres laufen.
  • Mauszeiger über Ausgaben per NSCursor.hide/unhide (gezählt, ausbalanciert), nicht setHiddenUntilMouseMoves.

M2 – Ton-Matrix (umgesetzt 2026-10-21, Langzeittest offen)

  • Core Audio direkt (AUHAL je Gerät) statt AVAudioEngine: einzelne Kanäle beschreiben, mehrere Quellen pro Kanal mischen, Render-Callback garantiert ohne Allokation.
  • Lock-freier Ringpuffer in C (stdatomic), weil Swifts Atomic erst ab macOS 15 verfügbar ist.
  • NDI liefert beim Verbinden einen Rückstau (~140 ms). Nach dem Vorfüllen wird einmalig auf die Ziel-Latenz gekürzt, sonst startet der Ton mit zu viel Verzögerung.
  • OBS/DistroAV sendet Audio in 1024er-Blöcken mit bis zu ~36 ms Lücke. 50 ms Puffer gaben vereinzelte Unterläufe → NDI-Routen 80 ms, Mikro-Routen 12 ms.
  • Drift-Regler langsam auslegen (Sekunden): der Füllstand ist durch die Blöcke ein Sägezahn. Im Betrieb ±150 ppm Korrektur, 0 Aussetzer in 2 min auf MOTU Out 2, CPU 0,3 %.
  • Entfernte Routen werden 2 s auf dem Main Thread geparkt, damit der Render-Thread nie den letzten Verweis loslässt (sonst free im Callback).
  • Sprechtaste global per Carbon-Hotkey (⌃⌥ Leertaste) – braucht keine Bedienungshilfen-Freigabe und meldet Drücken und Loslassen.

M4 – Intercom Stufe 1 (umgesetzt 2026-10-22, Abnahme offen)

  • WebRTC ohne Drittanbieter (Entscheidung 2026-10-21): Musubi ist ICE-lite-Gegenstelle und DTLS-Server. Eigene, schmale Umsetzung auf CryptoKit/CommonCrypto: STUN (RFC 5389/8445), DTLS 1.2 mit genau einer Cipher-Suite (ECDHE-ECDSA-AES128-GCM-SHA256) + use_srtp, SRTP AES-CM-HMAC-SHA1-80. Network.framework/Security bieten kein DTLS-SRTP.
  • Geprüft gegen RFC-Testvektoren (5769, 3711), OpenSSL (s_client -dtls1_2 -use_srtp, gleiche exportierte SRTP-Schlüssel, fremdes Zertifikat wird abgelehnt) und headless Chrome (Ende-zu-Ende: verbunden in ~12 ms, H.264 dekodiert, Opus empfangen). Opt-in-Test: MUSUBI_E2E=1 swift test --filter BrowserEndToEnd.
  • Das Client-Zertifikat des Browsers wird gegen den Fingerprint aus dem SDP-Angebot geprüft; ohne Übereinstimmung kein Schlüssel.
  • Signalisierung per HTTP-POST (Angebot rein, Antwort raus), keine Kandidaten vom Handy nötig: ICE-lite lernt die Adresse aus der ersten STUN-Prüfung (mDNS-Kandidaten der Browser stören nicht).
  • Vorschau: NDI-Proxy-Stream (bandwidth lowest) → 960×540, 25 fps, H.264 Constrained Baseline, ~1,2 Mbit/s, Keyframe alle 2 s und auf PLI.
  • Talkback: Abgriff am Headset-Mikro (AudioMatrix.addInputTap), Sprechtaste schaltet; sonst Stille, damit der Ton-Takt der Handys weiterläuft. Opus über AudioToolbox, 20 ms, 32 kbit/s.
  • Ohne verbundenes Handy wird nichts kodiert.

M3 – Szenen und Fernsteuerung (bestanden 2026-10-21)

  • Szene = Konfiguration der Regie + Konfiguration je Knoten. Bildschirme werden über den Namen erkannt (Position nur bei gleichnamigen Monitoren), weil CGDirectDisplayID je Rechner/Neustart wechselt.
  • Musubi speichert den Live-Stand laufend (~/Library/Application Support/Musubi/live-state.json) und stellt ihn beim Start wieder her – nicht die zuletzt gespeicherte Szene. Ausgaben für fehlende Bildschirme bleiben vorgemerkt und starten, sobald der Bildschirm erscheint.
  • Die Regie ist maßgeblich: Beim (Wieder-)Verbinden schickt sie ihre Soll-Belegung an den Knoten. Kennt sie den Knoten noch nicht, übernimmt sie dessen Stand.
  • Bonjour braucht die App in /Applications. In privaten Ordnern (~/Applications, Build-Ordner) kann mDNSResponder die Info.plist nicht lesen und lehnt NSBonjourServices ab (NoAuth -65555). NDI ist nicht betroffen (eigenes Multicast).
  • Direkt nach dem Start meldet macOS einige Sekunden „Local network prohibited“. Verbindungen brauchen daher Zeitgrenze + Wiederholung (6 s / 3 s).
  • Signatur mit Developer ID + Hardened Runtime: Freigaben (lokales Netzwerk, Mikrofon) bleiben über Builds erhalten. Mikrofon braucht das Entitlement com.apple.security.device.audio-input.
  • Als Anmeldeobjekt startet macOS die App ohne Fenster. Alles Wichtige startet deshalb in applicationDidFinishLaunching, nicht in einer SwiftUI-.task.
  • Der Wechsel auf 50 Hz löst synchron „Bildschirme geändert“ aus. Ohne Schutz startete jede Ausgabe doppelt (zweites Fenster blieb nach „Beenden“ stehen). Ausgabe jetzt vor dem Moduswechsel eintragen, Abgleich nie verschachtelt.
  • Offen: Beim ersten Neustart-Versuch hatte die Regie kurz vorher „keine Ausgaben“ für den Knoten gespeichert – Ursache nicht reproduzierbar (evtl. Klick auf „Ausgabe beenden“). Jede Änderung wird jetzt protokolliert (Kategorie scene).

Powered by TurnKey Linux.