1#![allow(dead_code)]
8#![allow(clippy::cast_precision_loss)]
9
10#[derive(Debug, Clone, Copy, PartialEq)]
15pub enum RcMode {
16 Cbr,
18 Vbr,
20 Crf,
22 Qp,
24}
25
26impl RcMode {
27 pub fn is_quality_based(self) -> bool {
30 matches!(self, RcMode::Crf | RcMode::Qp)
31 }
32
33 pub fn is_bitrate_constrained(self) -> bool {
35 !self.is_quality_based()
36 }
37
38 pub fn label(self) -> &'static str {
40 match self {
41 RcMode::Cbr => "CBR",
42 RcMode::Vbr => "VBR",
43 RcMode::Crf => "CRF",
44 RcMode::Qp => "QP",
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq)]
51pub struct BitrateTarget {
52 pub mode: RcMode,
54 pub target_kbps: u32,
56 pub min_kbps: u32,
58 pub max_kbps: u32,
60 pub quality_param: f32,
62}
63
64impl BitrateTarget {
65 pub fn cbr(kbps: u32) -> Self {
67 Self {
68 mode: RcMode::Cbr,
69 target_kbps: kbps,
70 min_kbps: kbps,
71 max_kbps: kbps,
72 quality_param: 0.0,
73 }
74 }
75
76 pub fn vbr(target_kbps: u32, min_kbps: u32, max_kbps: u32) -> Self {
78 Self {
79 mode: RcMode::Vbr,
80 target_kbps,
81 min_kbps,
82 max_kbps,
83 quality_param: 0.0,
84 }
85 }
86
87 pub fn crf(crf: f32) -> Self {
89 Self {
90 mode: RcMode::Crf,
91 target_kbps: 0,
92 min_kbps: 0,
93 max_kbps: 0,
94 quality_param: crf,
95 }
96 }
97
98 pub fn effective_kbps(&self) -> u32 {
102 if self.mode.is_quality_based() {
103 0
104 } else {
105 self.target_kbps
106 }
107 }
108
109 pub fn has_bitrate_budget(&self) -> bool {
111 self.effective_kbps() > 0
112 }
113}
114
115#[derive(Debug, Clone)]
121pub struct BitrateModelEstimator {
122 bpp_coefficient: f64,
124}
125
126impl BitrateModelEstimator {
127 pub fn new() -> Self {
129 Self {
130 bpp_coefficient: 0.07,
131 }
132 }
133
134 pub fn with_coefficient(bpp_coefficient: f64) -> Self {
136 Self { bpp_coefficient }
137 }
138
139 #[allow(clippy::cast_precision_loss)]
143 pub fn estimate_for_resolution(&self, width: u32, height: u32, fps: f64) -> u32 {
144 let pixels = width as f64 * height as f64;
145 let bits_per_sec = pixels * fps * self.bpp_coefficient;
146 (bits_per_sec / 1000.0).round() as u32
147 }
148
149 pub fn estimate_for_preset(&self, preset: &str) -> Option<u32> {
151 match preset {
152 "720p30" => Some(self.estimate_for_resolution(1280, 720, 30.0)),
153 "720p60" => Some(self.estimate_for_resolution(1280, 720, 60.0)),
154 "1080p30" => Some(self.estimate_for_resolution(1920, 1080, 30.0)),
155 "1080p60" => Some(self.estimate_for_resolution(1920, 1080, 60.0)),
156 "4k30" => Some(self.estimate_for_resolution(3840, 2160, 30.0)),
157 "4k60" => Some(self.estimate_for_resolution(3840, 2160, 60.0)),
158 _ => None,
159 }
160 }
161}
162
163impl Default for BitrateModelEstimator {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_rc_mode_cbr_not_quality_based() {
175 assert!(!RcMode::Cbr.is_quality_based());
176 }
177
178 #[test]
179 fn test_rc_mode_crf_is_quality_based() {
180 assert!(RcMode::Crf.is_quality_based());
181 }
182
183 #[test]
184 fn test_rc_mode_qp_is_quality_based() {
185 assert!(RcMode::Qp.is_quality_based());
186 }
187
188 #[test]
189 fn test_rc_mode_vbr_is_bitrate_constrained() {
190 assert!(RcMode::Vbr.is_bitrate_constrained());
191 }
192
193 #[test]
194 fn test_rc_mode_labels() {
195 assert_eq!(RcMode::Cbr.label(), "CBR");
196 assert_eq!(RcMode::Crf.label(), "CRF");
197 assert_eq!(RcMode::Qp.label(), "QP");
198 }
199
200 #[test]
201 fn test_bitrate_target_cbr_effective_kbps() {
202 let t = BitrateTarget::cbr(5000);
203 assert_eq!(t.effective_kbps(), 5000);
204 assert!(t.has_bitrate_budget());
205 }
206
207 #[test]
208 fn test_bitrate_target_crf_effective_kbps_is_zero() {
209 let t = BitrateTarget::crf(23.0);
210 assert_eq!(t.effective_kbps(), 0);
211 assert!(!t.has_bitrate_budget());
212 }
213
214 #[test]
215 fn test_bitrate_target_vbr_bounds() {
216 let t = BitrateTarget::vbr(4000, 1000, 8000);
217 assert_eq!(t.min_kbps, 1000);
218 assert_eq!(t.max_kbps, 8000);
219 }
220
221 #[test]
222 fn test_estimator_1080p30_reasonable() {
223 let est = BitrateModelEstimator::new();
224 let kbps = est.estimate_for_resolution(1920, 1080, 30.0);
225 assert!(kbps > 3000 && kbps < 6000, "Unexpected kbps: {kbps}");
227 }
228
229 #[test]
230 fn test_estimator_4k_higher_than_1080p() {
231 let est = BitrateModelEstimator::new();
232 let kbps_1080 = est.estimate_for_resolution(1920, 1080, 30.0);
233 let kbps_4k = est.estimate_for_resolution(3840, 2160, 30.0);
234 assert!(kbps_4k > kbps_1080);
235 }
236
237 #[test]
238 fn test_estimator_preset_720p30() {
239 let est = BitrateModelEstimator::new();
240 let kbps = est.estimate_for_preset("720p30").expect("should succeed");
241 let manual = est.estimate_for_resolution(1280, 720, 30.0);
242 assert_eq!(kbps, manual);
243 }
244
245 #[test]
246 fn test_estimator_preset_unknown_returns_none() {
247 let est = BitrateModelEstimator::new();
248 assert!(est.estimate_for_preset("8k120").is_none());
249 }
250
251 #[test]
252 fn test_estimator_custom_coefficient() {
253 let est = BitrateModelEstimator::with_coefficient(0.14);
254 let kbps_default = BitrateModelEstimator::new().estimate_for_resolution(1920, 1080, 30.0);
255 let kbps_custom = est.estimate_for_resolution(1920, 1080, 30.0);
256 assert!(kbps_custom > kbps_default);
258 }
259
260 #[test]
261 fn test_estimator_default_impl() {
262 let _est: BitrateModelEstimator = Default::default();
263 }
264}