lvqr_transcode/transcoder.rs
1//! [`Transcoder`] trait + [`TranscoderFactory`] + [`TranscoderContext`].
2//!
3//! Mirrors [`lvqr_agent::Agent`] / [`lvqr_agent::AgentFactory`]
4//! with a [`RenditionSpec`] carried through the context, so a
5//! single factory type (e.g. `SoftwareTranscoderFactory` in
6//! session 105 B) can be instantiated multiple times, once per
7//! rendition, to produce an ABR ladder.
8
9use lvqr_fragment::{Fragment, FragmentMeta};
10
11use crate::rendition::RenditionSpec;
12
13/// Snapshot of the `(broadcast, track, FragmentMeta, rendition)`
14/// tuple a fresh [`Transcoder`] sees at construction time.
15///
16/// `meta` is a snapshot at transcoder-build time; see the same
17/// caveat as [`lvqr_agent::AgentContext::meta`]. For the 4.6
18/// software / hardware encoders the codec + timescale never
19/// change mid-broadcast, so the snapshot is sufficient.
20#[derive(Debug, Clone)]
21pub struct TranscoderContext {
22 /// Source broadcast name (`<app>/<name>`), e.g. `"live/cam1"`.
23 ///
24 /// Session 105 B's output broadcast name is derived as
25 /// `<broadcast>/<rendition.name>` (e.g. `live/cam1/720p`).
26 pub broadcast: String,
27
28 /// Source track name. Typically `"0.mp4"` for video; session
29 /// 104 A filters non-video tracks at the factory level via
30 /// [`PassthroughTranscoderFactory::build`](crate::PassthroughTranscoderFactory).
31 pub track: String,
32
33 /// Codec, timescale, and (optionally) the init segment of the
34 /// source track at transcoder-build time.
35 pub meta: FragmentMeta,
36
37 /// Target rendition for this transcoder instance. One
38 /// transcoder + one rendition per ladder rung.
39 pub rendition: RenditionSpec,
40}
41
42/// In-process consumer of source [`Fragment`] values for one
43/// `(broadcast, track, rendition)` tuple. The 104 A trait is
44/// observe-only; 105 B extends the concrete implementations with
45/// an output-publish side without changing the trait surface.
46///
47/// Lifecycle is identical to [`lvqr_agent::Agent`]:
48///
49/// * [`Transcoder::on_start`] runs exactly once before the first
50/// [`Transcoder::on_fragment`].
51/// * [`Transcoder::on_fragment`] fires for every source fragment.
52/// * [`Transcoder::on_stop`] runs exactly once after the source
53/// [`lvqr_fragment::BroadcasterStream`] closes (every producer
54/// clone dropped).
55///
56/// All three calls are wrapped in
57/// `std::panic::catch_unwind(AssertUnwindSafe(..))` by the
58/// [`crate::TranscodeRunner`]; see the crate-level docs.
59///
60/// The trait is sync. Transcoders that want async or blocking
61/// work (every real gstreamer pipeline) spawn from inside
62/// [`Transcoder::on_start`] with a bounded channel to a worker
63/// thread / task -- typical pattern for the 105 B
64/// `SoftwareTranscoder`.
65pub trait Transcoder: Send {
66 /// One-shot setup. Called exactly once before the first
67 /// [`Transcoder::on_fragment`]. Default: no-op.
68 fn on_start(&mut self, _ctx: &TranscoderContext) {}
69
70 /// Process one source fragment.
71 ///
72 /// Synchronous. Heavy work MUST be offloaded to a worker
73 /// thread spawned in `on_start`; blocking here back-pressures
74 /// the per-broadcast drain task with
75 /// [`lvqr_fragment::FragmentBroadcaster`]'s documented
76 /// `RecvError::Lagged` skip semantics.
77 fn on_fragment(&mut self, fragment: &Fragment);
78
79 /// One-shot teardown after the source broadcaster closes.
80 /// Default: no-op.
81 ///
82 /// `on_stop` does NOT fire when the
83 /// [`crate::TranscodeRunnerHandle`] is dropped mid-stride (the
84 /// spawned task is aborted). Matches the
85 /// [`lvqr_agent::AgentRunner`] shutdown shape.
86 fn on_stop(&mut self) {}
87}
88
89/// Factory that builds a [`Transcoder`] for one specific
90/// rendition of one specific `(broadcast, track)` stream.
91///
92/// One factory instance per rendition: for an ABR ladder of three
93/// rungs the caller registers three factory instances on the
94/// [`crate::TranscodeRunner`], each carrying its own
95/// [`RenditionSpec`]. The factory either returns a fresh
96/// `Box<dyn Transcoder>` (one instance per source stream the
97/// factory opts into) or `None` to skip this stream.
98///
99/// `Send + Sync + 'static` so the factory lives behind an `Arc`
100/// shared across the registry callback's worker thread.
101pub trait TranscoderFactory: Send + Sync + 'static {
102 /// Stable identifier used in metric labels and logs (e.g.
103 /// `"passthrough"`, `"x264"`, `"nvenc"`). Pick something
104 /// short, lowercase, snake_case.
105 fn name(&self) -> &str;
106
107 /// Target rendition this factory produces. Consumed by the
108 /// [`crate::TranscodeRunner`] when building the
109 /// [`TranscoderContext`].
110 fn rendition(&self) -> &RenditionSpec;
111
112 /// Build a fresh transcoder for `ctx`, or return `None` to
113 /// skip this `(broadcast, track)` entirely. Returning `None`
114 /// is the correct path when the factory wants to opt out --
115 /// e.g. a video transcoder returning `None` for an audio
116 /// track, or a factory targeting only source broadcasts (not
117 /// already-transcoded renditions).
118 fn build(&self, ctx: &TranscoderContext) -> Option<Box<dyn Transcoder>>;
119}