lvqr_transcode/lib.rs
1//! Server-side transcoding for LVQR.
2//!
3//! **Tier 4 item 4.6, session 104 A scaffold.** This is the crate
4//! referenced by `tracking/TIER_4_PLAN.md` section 4.6. The goal is
5//! to let LVQR generate an ABR ladder (720p / 480p / 240p by
6//! default) from a single high-resolution source broadcast, with
7//! the output renditions re-injected into the local
8//! [`lvqr_fragment::FragmentBroadcasterRegistry`] so every egress
9//! surface (LL-HLS, DASH, MoQ relay, archive) serves them as if
10//! they had been ingested directly.
11//!
12//! # Session 104 A scope
13//!
14//! Scaffold + one pass-through transcoder:
15//!
16//! * [`Transcoder`] trait + [`TranscoderFactory`] + [`TranscoderContext`]
17//! modeled on the `lvqr-agent` crate's
18//! [`Agent`](lvqr_fragment::FragmentBroadcasterRegistry) shape so
19//! operators see one consistent "subscriber-with-lifecycle"
20//! idiom across WASM filters, AI agents, and transcoders.
21//! * [`TranscodeRunner`] + [`TranscodeRunnerHandle`]: registry-side
22//! installer + cheaply-cloneable handle, exactly mirroring
23//! `AgentRunner` in `lvqr-agent`.
24//! * [`RenditionSpec`] with width / height / bitrate fields and
25//! three preset constructors ([`RenditionSpec::preset_720p`],
26//! [`RenditionSpec::preset_480p`], [`RenditionSpec::preset_240p`])
27//! plus [`RenditionSpec::default_ladder`] for the LVQR default
28//! 3-rung ladder.
29//! * [`PassthroughTranscoder`] + [`PassthroughTranscoderFactory`]:
30//! the 104 A concrete implementation. Logs + counts per-fragment
31//! but does NOT actually encode or republish output. Exists to
32//! prove the end-to-end wiring (`FragmentBroadcasterRegistry`
33//! callback -> drain task -> per-rendition transcoder
34//! instance) before the `gstreamer-rs` pipelines land in
35//! session 105 B.
36//!
37//! # What session 105 B adds
38//!
39//! * Real `gstreamer-rs` pipelines gated behind a
40//! `transcode` Cargo feature (default OFF so CI runners without
41//! gstreamer plugins continue to build).
42//! * A `SoftwareTranscoder` using `appsrc -> qtdemux -> h264parse
43//! -> avdec_h264 -> videoscale -> x264enc -> ... -> mp4mux ->
44//! appsink` (plus passthrough audio) and re-injecting the output
45//! into the caller-supplied
46//! [`lvqr_fragment::FragmentBroadcasterRegistry`] as a new
47//! broadcast named `<source>/<rendition>` (e.g. `live/foo/720p`).
48//! * Optional hardware-encoder backends behind per-encoder feature
49//! flags (`hw-nvenc`, `hw-vaapi`, `hw-qsv`, `hw-videotoolbox`).
50//!
51//! # What session 106 C adds
52//!
53//! * `lvqr-cli` wiring (`--transcode-rendition 720p,480p,240p`
54//! flag + `ServeConfig::transcode_renditions`).
55//! * LL-HLS master playlist composition: the HLS bridge learns
56//! about source -> rendition relationships so one master
57//! playlist references every rendition as a variant with
58//! `BANDWIDTH` / `RESOLUTION` matching
59//! [`RenditionSpec`].
60//! * [`AudioPassthroughTranscoderFactory`]: always-available sibling of
61//! [`SoftwareTranscoderFactory`] that copies `<source>/1.mp4`
62//! fragments verbatim into `<source>/<rendition>/1.mp4` so each
63//! rendition broadcaster is a self-contained mp4 the LL-HLS bridge
64//! drains without special-casing the missing audio.
65//! * End-to-end demo: ingest one 1080p RTMP stream, watch the
66//! LL-HLS master playlist advertise four variants
67//! (source + three ladder rungs).
68//!
69//! # Anti-scope (session 104 A)
70//!
71//! * **No `lvqr-cli` wiring.** 106 C owns the composition root.
72//! * **No gstreamer dependency.** 105 B owns the real pipeline.
73//! Session 104 A ships a pass-through that exists only to
74//! prove the `FragmentBroadcasterRegistry` subscribe /
75//! drain / panic-isolation wiring without pulling a heavy C
76//! dep into the workspace build.
77//! * **No output re-publish.** 104 A transcoders are observers
78//! only. Session 105 B adds the output side.
79//! * **No config-file / admin-API ladder override.** 105 B +
80//! 106 C own operator-facing configuration.
81//! * **No HLS master-playlist integration.** 106 C owns the
82//! egress wiring.
83//!
84//! # Where this crate fits in the consumer family
85//!
86//! Pattern-matches the five existing
87//! [`lvqr_fragment::FragmentBroadcasterRegistry`] consumers:
88//!
89//! | Crate | Wires | Purpose |
90//! |-------|-------|---------|
91//! | `lvqr_cli::hls::BroadcasterHlsBridge` | `on_entry_created` | LL-HLS playlist composition |
92//! | `lvqr_cli::archive::BroadcasterArchiveIndexer` | `on_entry_created` | DVR archive index + on-disk segments |
93//! | `lvqr_wasm::install_wasm_filter_bridge` | `on_entry_created` | Per-fragment WASM filter tap |
94//! | `lvqr_cli::cluster_claim::install_cluster_claim_bridge` | `on_entry_created` | Renew cluster broadcast claim |
95//! | `lvqr_agent::AgentRunner` | `on_entry_created` | Per-broadcast user-defined agents |
96//! | **`lvqr_transcode::TranscodeRunner`** (new) | `on_entry_created` | Per-broadcast ABR-ladder transcoders |
97//!
98//! No new abstractions invented: the trait surface is a
99//! one-method generalisation of [`lvqr_agent`]'s `Agent` /
100//! `AgentFactory` / `AgentRunner`, re-shaped so each factory
101//! carries its own [`RenditionSpec`]. Every existing consumer
102//! already encodes the same subscribe / drain / panic-isolate
103//! pattern by hand.
104
105mod audio_passthrough;
106mod passthrough;
107mod rendition;
108mod runner;
109mod transcoder;
110
111#[cfg(feature = "transcode")]
112mod aac_opus;
113#[cfg(feature = "transcode")]
114mod software;
115#[cfg(feature = "transcode")]
116pub mod test_support;
117
118pub use audio_passthrough::{AudioPassthroughTranscoder, AudioPassthroughTranscoderFactory};
119pub use passthrough::{PassthroughTranscoder, PassthroughTranscoderFactory};
120pub use rendition::RenditionSpec;
121pub use runner::{TranscodeRunner, TranscodeRunnerHandle, TranscoderStats};
122pub use transcoder::{Transcoder, TranscoderContext, TranscoderFactory};
123
124#[cfg(feature = "transcode")]
125pub use aac_opus::{AacAudioConfig, AacToOpusEncoder, AacToOpusEncoderFactory, OpusFrame};
126#[cfg(feature = "transcode")]
127pub use software::{SoftwareTranscoder, SoftwareTranscoderFactory};