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