Skip to main content

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};