ExplicacionArquitectura del sistema

Motor de medios y plan de reproducción

El motor dual Zig y libav del daemon y el plan de reproducción composicional que decide qué hace cada uno.

Implementado. track/media-engine está en curso y outcome/playback-core (el planificador completo) todavía no ha empezado como outcome: lo que existe es el planificador de @styx/playback-policy que usa playback-svc. El estado de cada gate está en el roadmap.

Dos motores, un contrato

El daemon hace demux, remux y empaquetado fMP4-CMAF/HLS con motor propio en Zig y, de forma configurable, con libav (FFmpeg 8) enlazado como librería (dec-0110). Los dos implementan el mismo contrato MediaEngine (en native/zig/media-core/engine/):

  • probe: hechos del contenedor y de las pistas, con la misma estructura en ambos motores;
  • openDemuxer: paquetes comunes (pista, pts/dts con base de tiempo racional, marca de fotograma clave, datos con dueño explícito);
  • openRemuxer: escribe paquetes y devuelve fragmentos;
  • cancelación por petición, no global, y un único conjunto de errores (EngineError): ningún código AVERROR sale del puente.

La generación de listas HLS/CMAF y la segmentación por tiempo son código común a los dos motores (native/zig/transmux). Cada motor sólo aporta demux y mux de fragmentos, de modo que comparar uno con otro aísla lo que de verdad difiere.

Dónde vive cada cosa

CrateContenido
media-core/container/Parsers ISOBMFF y Matroska en Zig y los hechos de contenedor
media-core/engine/El contrato MediaEngine y la base de tiempo
transmux/Muxer fMP4-CMAF, empaquetador HLS común y registro de selección de motor
libav-bridge/El único sitio con @cImport de FFmpeg; sólo se compila con -Dlibav=true

transmux nunca importa libav-bridge. Sin la opción, el puente compila una versión desactivada.

FFmpeg nunca es un proceso

La regla es doble: FFmpeg sólo entra como librería, y ningún motor abre ficheros ni URLs por su cuenta. libav recibe un contexto de E/S propio cuyos callbacks leen de la fuente del daemon con el propósito correcto (probe, playback, seek). La guarda check:no-media-spawn falla si algún fichero de producto lanza ffmpeg o ffprobe. FFmpeg se compila como LGPL.

Como el código C de libav tiene historial de CVEs, el motor libav se trata como código no memory-safe: los parsers de entrada no confiable en Zig son seguros por construcción (ReleaseSafe, lectores acotados, fuzz), y el aislamiento de libav en un proceso trabajador es un invariante de diseño de dec-0117 (I6).

Selección de motor

El control plane decide, el daemon valida y ejecuta:

  • Configuración del daemon: media.engine = zig | libav | auto, y un fallback opcional hacia libav sólo con auto.
  • Por sesión, el plan lleva el motor pedido. auto usa el motor Zig si declara la capacidad para (contenedor, códec, destino) y si no, libav.
  • El fallback en caliente sólo existe si está configurado, y nunca es silencioso: lleva contador por motivo, atributo en la traza y aparece en la explicación del plan.
  • El motor usado es un atributo obligatorio de la telemetría (media.engine).

Conformidad diferencial

Un conjunto de pruebas diferenciales corre el mismo corpus (fijado por hash) por los dos motores y compara hechos, paquetes y fragmentos. Cada discrepancia se clasifica: fallo del motor Zig, laxitud de libav o diferencia legítima. Un canario con un oráculo desplazado debe producir discrepancias, para demostrar que la prueba discrimina.

El plan de reproducción

playback-svc convierte una intención (qué asset, qué cliente, qué preferencias) en un PlaybackPlan composicional y explicable, con las reglas puras de @styx/playback-policy:

PlaybackIntent + capacidades del cliente + capacidades de la fuente
        │
        ▼  planPlayback (sin E/S)
PlaybackPlan { source, container, video, audio, subtitles, delivery, reasons, estimatedCost, mode }
  • Las acciones son ortogonales por pista: contenedor (copy, remux, package), vídeo (copy, transcode, con filtros), audio (copy, transcode) y subtítulos (none, passthrough, convert, burn). Una sesión real puede necesitar un remux, un transcode de audio y una conversión de subtítulos a la vez.
  • mode (direct, adapted, video-transcode) es una clasificación derivada para la interfaz y la telemetría, nunca una entrada.
  • reasons explica cada decisión por pista, incluida la del motor de medios y su sustitución.
  • Los tipos del plan son contrato canónico en @styx/api-contracts; la lógica vive en @styx/playback-policy, que depende de los contratos y no al revés.

Hoy @styx/capability-model describe las capacidades de códec, contenedor, audio y subtítulos como enumeraciones TypeScript fijas (VIDEO_CODECS, AUDIO_CODECS…) y el emparejamiento entre cliente y fuente (matching.ts). Objetivo, sin código todavía (r23):

  • Que esas capacidades, y las de protocolo y transporte, sean datos versionados y no categorías estructurales, de modo que añadir un códec sea añadir una capacidad. Hoy añadir uno es cambiar el tipo, y no hay versión de capacidades.
  • La política canónica de códecs (CanonicalCodecPolicy v1): AV2 como frontera preferida, AV1 como respaldo de producción y HEVC Main10 por compatibilidad. Ningún paquete la implementa, y AV2 no está en VIDEO_CODECS.