Skip to main content

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}