Skip to main content

moq_transcode/
config.rs

1//! Transcoder configuration: the rung ladder and catalog wiring.
2
3use moq_net::{AsPath, PathRelativeOwned};
4
5#[doc(hidden)]
6#[deprecated(note = "use moq_net::Path::relative")]
7pub fn source_reference(source: impl AsPath, output: impl AsPath) -> Option<PathRelativeOwned> {
8	let source = source.as_path();
9	let output = output.as_path();
10	if output.strip_prefix(&source)?.is_empty() {
11		return None;
12	}
13
14	source.relative(&output)
15}
16
17/// One candidate output rendition: a target resolution (by height) and bitrate.
18///
19/// The width is derived from the source aspect ratio at runtime, and a rung is
20/// only offered when it is strictly below the source (see [`Config::rungs`]).
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22#[non_exhaustive]
23pub struct Rung {
24	/// Output height in pixels. Rounded down to even (I420 chroma is 2x2).
25	pub height: u32,
26
27	/// Target bitrate in bits per second: the CBR target and the bitrate
28	/// advertised in the derivative catalog.
29	pub bitrate: u64,
30}
31
32impl Rung {
33	/// A rung at `height` pixels and `bitrate` bits per second.
34	pub fn new(height: u32, bitrate: u64) -> Self {
35		Self { height, bitrate }
36	}
37}
38
39/// Transcoder configuration for [`run`](crate::run).
40///
41/// `#[non_exhaustive]`: build via `Config::default()` and set fields, so future
42/// knobs don't break callers.
43#[derive(Clone, Debug)]
44#[non_exhaustive]
45pub struct Config {
46	/// Candidate output renditions. Only rungs strictly below the source
47	/// survive: a rung is dropped when its height exceeds the source, when its
48	/// bitrate is not below the source bitrate (when known), or when it matches
49	/// the source height without a known source bitrate to undercut. A 480p
50	/// source is never transcoded up to 720p.
51	pub rungs: Vec<Rung>,
52
53	/// Where the source broadcast lives relative to the output broadcast, e.g.
54	/// `"."` when the output is published at `<source>/transcode.hang`. When
55	/// set, the derivative catalog references the source renditions (all video
56	/// and audio) through this path so players fetch them from the source
57	/// directly; the transcoder never proxies or subscribes them. `None` omits
58	/// them from the derivative catalog.
59	pub source: Option<PathRelativeOwned>,
60
61	/// Which video encoder implementation encodes the rungs. The default
62	/// prefers hardware (NVENC on Linux, VideoToolbox on macOS, Media
63	/// Foundation on Windows) and falls back to openh264.
64	pub encoder: moq_video::encode::Kind,
65
66	/// Which video decoder implementation decodes the source. The default
67	/// prefers hardware and falls back to openh264 (H.264 only; H.265 sources
68	/// need a hardware decoder).
69	pub decoder: moq_video::decode::Kind,
70
71	/// Frame resize behavior. Automatic mode keeps GPU-backed frames on the GPU.
72	pub resize: moq_video::resize::Config,
73}
74
75impl Default for Config {
76	fn default() -> Self {
77		Self {
78			// The default ladder, top rung first, filtered against the source at
79			// runtime so only strictly-lower renditions are offered.
80			rungs: vec![
81				Rung::new(1080, 5_000_000),
82				Rung::new(720, 2_500_000),
83				Rung::new(480, 1_200_000),
84				Rung::new(360, 600_000),
85				Rung::new(240, 350_000),
86			],
87			source: None,
88			encoder: moq_video::encode::Kind::default(),
89			decoder: moq_video::decode::Kind::default(),
90			resize: moq_video::resize::Config::default(),
91		}
92	}
93}
94
95#[cfg(test)]
96#[allow(deprecated)]
97mod tests {
98	use super::*;
99
100	#[test]
101	fn source_reference_normalizes_and_counts_output_depth() {
102		assert_eq!(source_reference("a/b", "a/b/transcode.hang").unwrap().as_str(), ".");
103		assert_eq!(source_reference("/a//b/", "a/b/dir/").unwrap().as_str(), ".");
104		assert_eq!(
105			source_reference("a/b", "a/b/dir/transcode.hang").unwrap().as_str(),
106			".."
107		);
108		assert_eq!(
109			source_reference("a/b", "a/b/one/two/transcode.hang").unwrap().as_str(),
110			"../.."
111		);
112		assert!(source_reference("a/b", "other/transcode.hang").is_none());
113		assert!(source_reference("a/b", "a/b").is_none());
114	}
115}